前言

Preface

这是every-layout.dev的电子书版本,包含所有相同的内容。我们为您制作它是因为我们认为您可能喜欢以这种格式离线阅读有关 CSS 布局的信息。

This is the ebook version of every-layout.dev and contains all of the same content. We made it for you because we thought you might like to read about CSS layout in this format, and offline.

EPUB 书籍不能做或展示网站可以做的所有事情,并且交互式演示被返回到every-layout.dev网站的链接所取代。要查看所有这些页面和演示,您必须购买Every Layout的完整版本。如果您正在看这本书,那么您很有可能已经做到了。购买后,指向完整的未锁定站点的链接将随电子邮件中的本书链接一起出现。

An EPUB book cannot do or show all the things a website can, and the interactive demos are replaced with links back to the every-layout.dev website. To see all of these pages and demos, you have to purchase the full version of Every Layout. If you are looking at this book, you have hopefully done that already. After purchase, a link to the full, unlocked site will accompany the link to this book in an email.

本书所有外部链接均标有↗符号/字符。如果您看到一个没有后缀 ↗ 的链接,它指向书中的一个章节。这本书已经过测试,可以在最新版本的 Apple 的 iBooks 和Calibre 电子书管理器中按预期呈现和运行。

All external links in this book are marked with a ↗ symbol/character. If you see a link not suffixed with ↗, it points to a section within the book itself. The book has been tested to render and behave as expected in the latest versions of Apple’s iBooks, and the Calibre e-book manager.

版本

Editions

第三版(当前)

Third edition (current)

该版本引入了逻辑属性,以更好地兼容不同的语言及其书写模式。它还更新了Frame组件以使用该aspect-ratio属性,该属性现已得到广泛支持。

This edition introduced logical properties for better compatibility with different languages and their writing modes. It also updated the Frame component to use the aspect-ratio property, which is now widely supported.

第二版

Second edition

此版本转换了许多布局以使用gapFlexbox 和 Grid 广泛支持的属性。使用gap简化了许多布局并使它们更易于理解。

This edition converted a number of layouts to use the gap property which has come to be widely supported with Flexbox as well as Grid. Using gap simplifies many layouts and makes them easier to understand.

第一版

First edition

实际上,我们在最初发布后添加了更多内容,但我们还没有开始记录“版本”,因此所有这些更新都隐含在第一版中。

We actually added a lot more content after the initial release but we hadn’t started recording “editions” so all of those updates are implicitly part of the first edition.

所有权

Ownership

当您购买Every Layout的许可时,您就拥有了由 Heydon Pickering 和 Andy Bell 创作和拥有的内容的许可。

When you purchase a licence for Every Layout, you own a license to the content that is authored and owned by Heydon Pickering and Andy Bell.

公平使用和再分配

Fair usage and redistribution

严禁重新发布和转售Every Layout,发现的情况将根据英国版权法合法追究。

Re-publishing and re-selling of Every Layout is strictly forbidden and discovered instances will be pursued, legally, in accordance with United Kingdom copyright law.

我们希望许可证持有者以公平的方式使用他们的许可证。我们非常信任我们的许可证持有者,因此我们可以尽可能顺畅地使用Every Layout 。我们相信您,作为许可证持有人,应该能够几乎没有障碍地访问您付费购买的内容,但这也意味着许可证很容易共享。

We expect licence holders to use their licence in a fair manner. We put a lot of trust in our licence holders so that we can make using Every Layout as frictionless as possible. We believe that you, the licence holder, should be able to access the content that you paid for with little to no barriers, but this also means that the licence is easily shared.

如果我们怀疑您没有以公平的方式使用您的许可证或不负责任地共享它,我们保留在发出合理警告后撤销您对Every Layout的访问权且不退款的权利。

If we suspect you are not using your license in a fair manner or sharing it irresponsibly, we reserve the right to revoke your access to Every Layout with no refunds, after a fair warning.

雏形

Rudiments

布局

Layouts

盒子

Boxes

正如雷切尔安德鲁提醒我们的那样,网页设计中的一切都是一个盒子,或者没有盒子。不一定所有东西看起来都像一个盒子border-radius—— , clip-path, 并且transforms可能具有欺骗性,但所有东西都占据了一个盒子般的空间。因此,布局不可避免地是盒子的排列。

As Rachel Andrew has reminded us, everything in web design is a box, or the absence of a box. Not everything necessarily looks like a box—border-radius, clip-path, and transforms can be deceptive, but everything takes up a box-like space. Layout is inevitably, therefore, the arrangement of boxes.

在着手组合框来制作复合布局之前,重要的是要熟悉框本身是如何按照标准设计的。

Before one can embark on combining boxes to make composite layouts, it is important to be familiar with how boxes themselves are designed to behave as standard.

盒子模型

The box model

框模型是布局框所基于的公式,包括内容、填充、边框和边距。CSS 让我们改变这些值来改变元素显示的整体大小和形状。

The box model is the formula upon which layout boxes are based, and comprises content, padding, border, and margin. CSS lets us alter these values to change the overall size and shape of elements’ display.

同心矩形从中心读取内容、填充、边框和边距

Concentric rectangles reading, from the center, content, padding, border, and margin

Web 浏览器有助于将默认 CSS 样式应用于某些元素,这意味着它们以合理可读的方式进行布局:即使未应用作者 CSS。

Web browsers helpfully apply default CSS styles to some elements, meaning they are laid out in a reasonably readable fashion: even where author CSS has not been applied.

在 Chrome 中,段落 ( ) 的默认用户代理样式<p>看起来像……

In Chrome, the default user agent styles for paragraphs (<p>) look like…

p {
  display: block;
  margin-block-start: 1em;
  margin-block-end: 1em;
  margin-inline-start: 0px;
  margin-inline-end: 0px;
}

...和无序列表 ( <ul>) 样式看起来像...

… and unordered list (<ul>) styles look like…

ul {
  display: block;
  list-style-type: disc;
  margin-block-start: 1em;
  margin-block-end: 1em;
  margin-inline-start: 0px;
  margin-inline-end: 0px;
  padding-inline-start: 40px;
}

display物业

The display property

在以上两个示例中,元素的display属性都设置为block. 块元素占据一维空间中的所有可用空间。通常,这是水平尺寸,因为writing-mode设置为horizontal-tb(水平;从上到下的流动方向)。在某些情况下,对于某些语言(如蒙古语),vertical-lr是合适的书写模式。

In both the above examples, the element's display property is set to block. Block elements assume all of the available space in one dimension. Typically, this is the horizontal dimension, because the writing-mode is set to horizontal-tb (horizontal; with a top to bottom flow direction). In some cases, and for some languages (like Mongolian), vertical-lr is the appropriate writing mode.

第一个示例显示了从上到下布局的块元素(水平-tb 书写模式)。 第二个显示它们从左到右排列(vertical-lr 书写模式)

The first example shows block elements laid out from top to bottom (horizontal-tb writing mode). The second shows them laid out from left to right (vertical-lr writing mode)

内联元素(具有displayvalue inline)表现不同。它们的布局符合当前的上下文、写作模式和方向。它们仅与其内容一样宽,并且只要有空间就可以相邻放置。块元素遵循流向,内联元素遵循书写方向。

Inline elements (with the display value inline) behave differently. They are laid out in line with the current context, writing mode, and direction. They are only as wide as their content, and are placed adjacently wherever there is space to do so. Block elements follow flow direction, and inline elements follow writing direction.

垂直堆叠的块元素,其中一个块元素包含(包装)内联元素。 横轴为书写方向,纵轴为流向

A vertical stack of block elements, with one block element containing (wrapping) inline elements. The horizontal axis is labeled writing direction and the vertical axis is labeled flow direction

从排版的角度来看,可以说块元素就像段落,而内联元素就像单词。

Thinking typographically, it could be said that block elements are like paragraphs, and inline elements are like words.

块元素(也称为块级元素)使您可以控制框的水平和垂直尺寸。也就是说,您可以将宽度、高度、边距和填充应用于块元素,它会生效。另一方面,内联元素的大小是固有的(规定widthheight值不生效)并且只允许水平边距和填充值。内联元素旨在符合其他内联元素之间水平放置的流程。

Block elements (also called block-level elements) afford you control over both the horizontal and vertical dimensions of the box. That is, you can apply width, height, margin, and padding to a block element and it will take effect. On the other hand, inline elements are sized intrinsically (prescribed width and height values do not take effect) and only horizontal margin and padding values are permitted. Inline elements are designed to conform to the flow of horizontal placement among other inline elements.

一个相对较新的显示属性 ,inline-block是 和 的混合blockinline。您可以在元素上设置垂直属性inline-block,尽管这并不总是可取的——如上图所示。

A relatively new display property, inline-block, is a hybrid of block and inline. You can set vertical properties on inline-block elements, although this is not always desirable—as the proceeding illustration demonstrates.

在左侧,内联元素的垂直边距被忽略,这意味着行的行高未被打断。 在右侧,行内块元素的边距已经打开了行高。

On the left, an inline element’s vertical margins are ignored, meaning line height of rows is undisrupted. On the right, an inline-block element’s margins have prised the line height open.

在基本display类型中,只剩下了none。该值将元素从布局中完全移除。它没有视觉存在,也不会影响周围元素的布局。就好像元素本身已经从 HTML 中删除了一样。因此,浏览器不会将元素的存在或内容传达给屏幕阅读器软件display: none等辅助技术。

Of the basic display types, only none remains. This value removes the element from the layout entirely. It has no visual presence, and no impact on the layout of surrounding elements. It is as if the element itself has been removed from the HTML. Accordingly, browsers do not communicate the presence or content of display: none elements to assistive technologies like screen reader software.

逻辑属性

Logical properties

什么是逻辑属性,它们的存在是否意味着不合逻辑的属性的存在?习惯于从左到右 ( direction: ltr) 和从上到下 ( writing-mode: horizontal-tb) 阅读的英语使用者发现在应用边距和填充等样式时使用包含单词“左”、“右”、“上”和“下”的属性是合乎逻辑的。

What are logical properties and does their existence imply the existence of illogical properties? English speakers accustomed to reading left to right (direction: ltr) and top to bottom (writing-mode: horizontal-tb) find it logical to use properties that include the words “left”, “right”, “top”, and “bottom” when applying styles like margin and padding.

.icon {
  margin-right: 0.5em;
}

当方向或书写模式改变时,这就变得不合逻辑了,因为左右(和/或顶部和底部)被翻转了。现在你放在右边的边距你真的需要放在左边。

It’s when the direction or writing mode changes that this becomes illogical, because left and right (and/or top and bottom) are flipped. Now the margin you put on the right you really need on the left.

在图像的左侧,对于从左到右的书写方向,边距正确地位于图标和文本之间。 在图像的右侧,对于从右到左的书写方向,页边距错误地位于右侧。 图标和文本之间没有边距。

On the left of the image, for left to right writing direction, the margin is correctly between the icon and text. On the right of the image, for right to left writing direction, the margin is incorrectly on the right hand side. There is no margin between the icon and text.

逻辑属性避免使用像“左”和“右”这样的术语,因为我们知道它们可以颠倒,使这些术语变得毫无意义。相反,我们根据块和内联方向应用边距、填充和边框等样式。

Logical properties eschew terminology like “left” and “right” because we know they can be reversed, making the terms a nonsense. Instead, we apply styles like margin, padding, and border according to the block and inline direction.

.icon {
  margin-inline-end: 0.5em;
}

在一个ltr方向上,margin-inline-end向右应用边距。在一个rtl方向上,margin-inline-end向左应用边距。在这两种情况下,它都应用于需要的地方:在线维度的末尾。

In a ltr direction, margin-inline-end applies margin to the right. In a rtl direction, margin-inline-end applies margin to the left. In both cases, it is applied where it is needed: at the end of the inline dimension.

在左侧,对于从左到右的书写方向,边距正确地位于图标和文本之间。 从右到左的方向也是如此,如图像右侧所示。

On the left, for left to right writing direction, the margin is correctly between the icon and text. The same is true for right to left direction, as seen on the right of the image.

格式化上下文

Formatting contexts

当您将display: flexor应用display: grid到 a<div>时,它继续表现得像一个块元素,使用display: block. 但是,它改变了其元素的行为方式。例如,仅display: flex(没有其他与 Flexbox 相关的属性)应用于父级,其子级将水平分布。或者,换句话说,流动方向从垂直切换到水平。

When you apply display: flex or display: grid to a <div>, it continues to behave like a block element, using display: block. However, it changes the way its child elements behave. For example, with just display: flex (and no other Flexbox-related properties) applied to the parent, its children will distribute themselves horizontally. Or, to put it another way, the flow direction is switched from vertical to horizontal.

格式化上下文是该项目中记录的许多布局的基础。它们将元素变成布局组件。在Composition中,我们将探讨如何嵌套不同的格式化上下文,以创建复合布局。

Formatting contexts are the basis of many of the layouts documented in this project. They turn elements into layout components. In Composition, we'll explore how different formatting contexts can be nested, to create composite layouts.

盒子里的东西

Content in boxes

Web 主要是文本信息的渠道,辅以图像和视频等媒体,通常统称为内容。浏览器结合了换行和滚动算法,以确保将内容完整地传输给用户,而不管他们的屏幕大小和尺寸以及缩放级别等设置如何。默认情况下,网络很大程度上是响应式的。

The web is a conduit for primarily textual information supplemented by media such as images and videos, often referred to collectively as content. Browsers incorporate line wrapping and scrolling algorithms to make sure content is transmitted to the user in its entirety, irrespective of their screen sizes and dimensions, and settings such as zoom level. The web is responsive largely by default.

在没有干预的情况下,元素的内容决定了它的大小和形状。内容使inline元素水平增长,block元素垂直增长。留给它自己的设备,盒子的面积由它包含的内容的面积决定。因为网页内容是动态的(可能会发生变化),所以网页布局的静态表示极具误导性。强烈建议从一开始就直接使用 CSS 及其灵活性,就像我们在这里一样。

Without intervention, it is the contents of an element that determines its size and shape. Content makes inline elements grow horizontally, and block elements grow vertically. Left to its own devices, the area of a box is determined by the area of the content it contains. Because web content is dynamic (subject to change), static representations of web layouts are extremely misleading. Working directly with CSS and its flexibility from the outset, as we are here, is highly recommended.

左边是一个包含一些文本内容的宽框。 右边是同一个框,但变窄了。 补偿内部具有相同数量内容的补偿更高。

On the left is a wide box containing some text content. On the right is the same box but narrowed. It is taller to compensate for having the same amount of content inside it.

图片说明:如果将元素的宽度减半,则它必须是原来的两倍高才能包含相同数量的内容

box-sizing物业

The box-sizing property

默认情况下,盒子的尺寸是盒子内容的尺寸加上它的填充和边框值(隐式:)box-sizing: content-box。也就是说,如果你将一个元素设置为10rem宽,然后在 的两边加上 padding 1rem,它就会是12rem宽的:10rem加上1remleft padding 和1remof right padding。如果您选择box-sizing: border-box,内容区域将缩小以容纳填充,并且总宽度等于规定width10rem

By default, the dimensions of a box are the dimensions of the box’s content plus its padding and border values (implicitly: box-sizing: content-box). That is, if you set an element to be 10rem wide, then add padding on both sides of 1rem, it will be 12rem wide: 10rem plus 1rem of left padding and 1rem of right padding. If you opt for box-sizing: border-box, the content area is reduced to accommodate the padding and the total width equals the prescribed width of 10rem.

两个盒子,每边都有填充物。 左边的更高更宽,带有 box-sizing content-box。 右边的有 box-sizing border-box。

Two boxes, both with padding on all sides. The left one is taller and wider with box-sizing content-box. The right one has box-sizing border-box.

border-box通常,对所有框使用该模型被认为是更可取的。它使计算/预测盒子尺寸更容易。

Generally, it is considered preferable to use the border-box model for all boxes. It makes calculating/anticipating box dimensions easier.

适用于所有元素的任何样式,如box-sizing: border-box,最好使用*(“通用”或“通配符”)选择器来应用。正如在全局和局部样式中详细介绍的那样,能够同时影响多个元素(在本例中为所有元素)的布局是 CSS 为布局设计带来效率的方式。

Any styles, like box-sizing: border-box, that are applicable to all elements are best applied using the * (“universal” or “wildcard”) selector. As covered in detail in Global and local styling, being able to affect the layout of multiple elements (in this case, all elements) simultaneously is how CSS brings efficiency to layout design.

* {
  box-sizing: border-box;
}

content-box只有当盒子的高度或宽度受到限制时,和之间的差异border-box才会发挥作用。为了便于说明,请考虑将一个块元素放置在另一个块元素内。使用content-box模型和 的填充1rem,子元素将在应用(相当于在书写模式中)2rem时溢出。inline-size: 100%width: 100%horizontal-tb

Only where the height or width of a box is constrained does the difference between content-box and border-box come into play. For illustration, consider a block element placed inside another block element. Using the content-box model and a padding of 1rem, the child element will overflow by 2rem when inline-size: 100% (equivalent to width: 100% in a horizontal-tb writing mode) is applied.

子元素被父元素的右边缘包围

The child element is boxing out over the parent element’s right edge

为什么?因为inline-size: 100%意思是“使这个元素的宽度与父元素的宽度相同”。由于我们使用的是模型content-box,因此内容100%变宽了,然后将填充添加到该值上。

Why? Because inline-size: 100% means “make the width of this element the same as the parent element”. Since we are using the content-box model, the content is made 100% wide, then the padding is added on to this value.

但是如果我们使用inline-size: auto(我们可以只删除inline-size: 100%,因为auto是默认值)子框完全适合父框。这与价值无关。box-sizing

But if we use inline-size: auto (we can just remove inline-size: 100%, since auto is the default value) the child box fits within the parent box perfectly. And that’s regardless of the box-sizing value.

子元素完全符合父元素的宽度

The child element fits exactly within the parent element’s width

隐含地,height也设置为auto,这意味着它是从内容派生的。再次,box-sizing没有效果。

Implicitly, the height is also set to auto, meaning it is derived from the content. Again, box-sizing has no effect.

这里的教训是我们元素的维度应该主要从它们的内部内容和外部上下文中导出。当我们试图规定尺寸时,事情往往会出错。作为视觉设计师,我们应该做的就是就布局应该如何形成提出建议。例如,我们可能会应用一个min-height(如在Cover布局中)或提供一个flex-basis(如在Sidebar中)。

The lesson here is the dimensions of our elements should be largely derived from their inner content and outer context. When we try to prescribe dimensions, things tend to go amiss. All we should be doing as visual designers is making suggestions as to how the layout should take shape. We might, for instance, apply a min-height (as in the Cover layout) or proffer a flex-basis (as in the Sidebar).

建议的 CSS 是算法布局设计的核心。我们允许浏览器进行自己的计算,得出自己的结论,而不是告诉浏览器做什么,以最适合用户、他们的屏幕和设备。在任何情况下,任何人都不应体验模糊的内容。

The CSS of suggestion is at the heart of algorithmic layout design. Instead of telling browsers what to do, we allow browsers to make their own calculations, and draw their own conclusions, to best suit the user, their screen, and device. Nobody should experience obscured content under any circumstances.

作品

Composition

如果你是一名程序员,你可能听说过组合优于继承原则。这个想法是,将简单的独立部分(对象、类、函数)组合起来比通过继承将所有东西都连接到一个共享的源点更灵活,并带来更高的效率。

If you are a programmer, you may have heard of the composition over inheritance principle. The idea is that combining simple independent parts (objects; classes; functions) gives you more flexibility, and leads to more efficiency, than connecting everything—through inheritance—to a shared origin.

组合优于继承不一定适用于“业务逻辑”。在前端架构和视觉设计中支持组合也是有益的(React 文档甚至有专门的页面介绍它)。

Composition over inheritance does not have to apply to “business logic”. It is also beneficial to favor composition in front-end architecture and visual design (the React documentation even has a dedicated page about it).

组成和布局

Composition and layout

要了解组合如何使布局系统受益,让我们考虑一个示例组件。假设这个组件是一个对话框,因为界面(出于我们现在不会讨论的原因)需要一个对话框。这是它的样子:

To understand how composition benefits a layout system, let’s consider an example component. Let’s say that this component is a dialog box, because the interface (for reasons we won’t get into right now) requires a dialog box. Here is what it looks like:

一个带有关闭按钮的对话框元素,中间有一条消息,还有两个按钮:确定和取消

A dialog element with a close button, a message in the center, and two buttons: okay and cancel

但它是怎么变成那样的呢?一种方法是编写一些专用的对话框 CSS。您可以为对话框提供一个“块”标识符(.dialog在 CSS 和class="dialog"HTML 中)并将其用作您的名称空间以附加样式声明。

But how does it get to look like that? One way is to write some dedicated dialog CSS. You might give the dialog box a “block” identifier (.dialog in CSS, and class="dialog" in HTML) and use this as your namespace to attach style declarations.

.dialog {
  /* ... */
}

.dialog__header {
  /* ... */
}

.dialog__body {
  /* ... */
}

.dialog__foot {
  /* ... */
}

或者,这些对话框样式可能是从第三方 CSS 库/框架导入的。在任何一种情况下,许多用于使对话框看起来像对话框的 CSS 都可以用于制作其他类似的布局。但是由于这里的所有内容都在 下命名空间.dialog,当我们开始制作下一个组件时,我们最终将复制可能共享的样式。这是大多数 CSS 膨胀的来源。

Alternatively, these dialog styles might be imported from a third-party CSS library/framework. In either case, a lot of the CSS used to make the dialog look like a dialog, could be used to make other, similar layouts. But since everything here is namespaced under .dialog, when we come to make the next component, we’ll end up duplicating would-be shared styles. This is where most CSS bloat comes from.

namespacing部分是这里的关键。继承心态鼓励我们在决定 UI 的哪些最终部分可以做什么之前考虑应该调用它们或者其他更小的部分可以为它们做什么。这就是合成的用武之地。

The namespacing part is key here. The inheritance mindset encourages us to think about what finalized parts of UI should be called before we’ve even decided what they do, or what other, smaller parts can do for them. That’s where composition comes in.

布局原语

Layout primitives

上一个示例中的错误是认为对话框表单的所有内容都是孤立和独特的,而实际上,它只是简单布局的组合。Every Layout的目的是识别和记录这些较小的布局中的每一个。我们一起称它们为primitives

The mistake in the last example was to think of everything about the dialog’s form as isolated and unique when, really, it's just a composition of simpler layouts. The purpose of Every Layout is to identify and document what each of these smaller layouts are. Together, we call them primitives.

术语原始具有语言、数学和计算内涵。在每种情况下,原语都是没有自己意义或目的的东西,但可以在组合中使用它来制作有意义的东西,或词汇。在语言中,它可能是一个词或短语,在数学中可能是一个方程式,在设计中可能是一个模式,或者在开发中可能是一个组件。

The term primitive has linguistic, mathematical, and computing connotations. In each case, a primitive is something without its own meaning or purpose as such, but which can be used in composition to make something meaningful, or lexical. In language it might be a word or phrase, in mathematics an equation, in design a pattern, or in development a component.

在 JavaScript 中,布尔数据类型是原始数据类型。仅在上下文之外查看值true(或false)并不能告诉您有关更大的 JavaScript 应用程序的信息。另一方面,对象数据类型不是原始数据类型。您不能在不指定自己的属性的情况下编写对象。因此,对象是有意义的;他们必然会告诉您作者的意图。

In JavaScript, the Boolean data type is a primitive. Just looking at the value true (or false) out of context tells you very little about the larger JavaScript application. The object data type, on the other hand, is not primitive. You cannot write an object without designating your own properties. Objects are therefore meaningful; they necessarily tell you something of the author’s intent.

对话框作为 UI 的一部分是有意义的,但它的组成部分却不是。以下是我们如何使用Every Layout 的布局原语来组合对话框:

The dialog is meaningful, as a piece of UI, but its constituent parts are not. Here’s how we might compose the dialog box using Every Layout’s layout primitives:

对话框被分成它的组成布局,使用 Cluster、Stack、Box 和 Center 布局基元成为可能

The dialog is divided into its constituent layouts, made possible with the Cluster, Stack, Box, and Center layout primitives

使用许多相同的原语,我们可以创建一个注册表单……

Using many of the same primitives, we can create a registration form…

具有三个字段和一个提交按钮的表单,该按钮由 Cluster、Stack、Box 和 Center 布局基元创建,包括用于标签和输入对的嵌套 Stack

A form with three fields and a submit button created from Cluster, Stack, Box, and Center layout primitives, including a nested Stack for label and input pairs

......或会议演讲的幻灯片布局:

… or a slide layout for a conference talk:

文本居中且上一个和下一个按钮与底部对齐的幻灯片。 由 Cover、Box、Stack 和 Sidebar 基元制成

A slide with centered text and previous and next buttons aligned to the bottom. Made from Cover, Box, Stack, and Sidebar primitives

如果没有原始数据类型,您将不得不不断地教您的编程语言如何进行基本操作。您很快就会忘记最初打算使用该语言完成的具体、有意义的任务。不利用基元的设计系统同样存在问题。如果模式库中的每个组件都遵循自己的布局规则,那么效率低下和不一致的情况就会比比皆是。

Without primitive data types, you would have to be constantly teaching your programming language how to do basic operations. You would quickly lose sight of the specific, meaningful task you set out to accomplish with the language in the first place. A design system that does not leverage primitives is similarly problematic. If every component in your pattern library follows its own rules for layout, inefficiencies and inconsistencies will abound.

每个图元都有一个简单的职责:“垂直空间元素”“均匀填充元素”“水平分隔元素”等。它们被设计用于组合,作为彼此的父母、孩子或兄弟姐妹。

The primitives each have a simple responsibility: "space elements vertically", "pad elements evenly", "separate elements horizontally", etc. They are designed to be used in composition, as parents, children, or siblings of one another.

您可能无法单独使用Every Layout 的 原语来创建每个布局。但您当然可以制作大多数(如果不是全部)常见的网页布局,并实现许多您自己的独特构想。

You probably cannot create literally every layout using Every Layout's primitives alone. But you can certainly make most, if not all, common web layouts, and achieve many of your own unique conceptions.

无论如何,您应该理解和欣赏组合的好处,以及使用少量可重用代码创建各种界面的能力。英文字母表只有 26 个字节,想想用它创作的所有伟大作品吧!

In any case, you should walk away with an understanding and appreciation for the benefits of composition, and the power to create all sorts of interfaces with just a little reusable code. The English alphabet is only 26 bytes, and think of all the great works created with that!

单位

Units

你在网络上看到的一切都是由构成你设备屏幕的小光点组成的:像素。因此,在衡量构成我们界面的人工制品时,以像素为单位进行思考并使用 CSSpx单位是有意义的。或者是吗?

Everything you see on the web is composed out of the little dots of light that make up your device’s screen: pixels. So, when measuring out the artefacts that make up our interfaces, thinking in terms of pixels, and using the CSS px unit, makes sense. Or does it?

屏幕的像素几何形状千差万别,大多数现代显示器都采用子像素渲染,即对单个像素的颜色分量进行处理,以平滑锯齿状边缘并产生更高的感知分辨率。的概念1px比通常描述的更模糊。

Screens’ pixel geometries vary wildly, and most modern displays employ sub-pixel rendering, which is the manipulation of the color components of individual pixels, to smooth jagged edges and produce a higher perceived resolution. The notion of 1px is fuzzier than how it’s often portrayed.

两个像素相邻。 它们各自由三个子像素组成,但两者的子像素长方形排列不同

Two pixels next to each other. They are each made up of three subpixels, but the arrangement of the subpixel oblongs differ between the two

图片说明:三星 Galaxy Tab S 10.5 交替排列像素之间的子像素。每个其他像素的组成都不同。

屏幕分辨率——屏幕包含多少像素——也不同。因此,虽然一个“CSS 像素”(1px在 CSS 中)可能近似于低分辨率屏幕上的一个“设备”或“硬件”像素,但高分辨率屏幕可能为每个CSS提供多个设备像素。1px所以有像素,然后有像素像素。

Screen resolutions—how many pixels screens pack—also differ. Consequently, while one “CSS pixel” (1px in CSS) may approximate one “device” or “hardware” pixel on a lower resolution screen, a high resolution screen may proffer multiple device pixels for each 1px of CSS. So there are pixels, and then there are pixels of pixels.

由 9 个大小相同的盒子组成的网格。 网格本身被标记为一个像素,组成它的框之一也是如此

A grid of 9 equally sized boxes. The grid itself is labeled as a pixel, and so is one of the boxes it is composed from

可以这么说,虽然屏幕确实由像素组成,但像素并不是规则的、不可变的或恒定的。400px用户浏览放大后看到的框根本不是400pxCSS 像素。甚至在他们激活缩放之前,它可能不在400px设备像素中。

Suffice it to say that, while screens are indeed made up of pixels, pixels are not regular, immutable, or constant. A 400px box viewed by a user browsing zoomed in is simply not 400px in CSS pixels. It may not have been 400px in device pixels even before they activated zoom.

px在 CSS 中使用单位本身并不是不正确的;您不会看到任何错误消息。但它鼓励我们在一个错误的前提下工作:像素完美是可以实现的,也是可取的。

Working with the px unit in CSS is not incorrect as such; you won’t see any error messages. But it encourages us to labour under a false premise: that pixel perfection is both attainable and desirable.

缩放和可访问性

Scaling and accessibility

使用该px单位进行设计不仅会鼓励我们采用错误的思维方式:还有明显的局限性。其一,当您使用 设置字体时px,浏览器假定您希望将字体固定为该大小。因此,用户在浏览器设置中选择的字体大小将被忽略。

Designing using the px unit doesn’t only encourage us to adopt the wrong mindset: there are manifest limitations as well. For one, when you set your fonts using px, browsers assume you want to fix the fonts at that size. Accordingly, the font size chosen by the user in their browser settings is disregarded.

随着现代浏览器现在支持整页缩放(其中包括文本在内的所有内容都按比例缩放),这通常被认为是一个已解决的问题。然而,正如Evan Minto 发现的那样,与浏览器 Edge 或 Internet Explorer 的用户相比,在浏览器设置中调整默认字体大小的用户更多。也就是说:忽视调整其默认字体大小的用户与忽视整个浏览器一样具有影响力。

With modern browsers now supporting full page zoom (where everything, including text is zoomed proportionately), this is often blown off as a solved problem. However, as Evan Minto discovered, there are more users who adjust their default font size in browser settings than there are users of the browsers Edge or Internet Explorer. That is: disregarding users who adjust their default font size is as impactful as disregarding whole browsers.

单位emremchex不存在这样的问题,因为它们都是对于用户在操作系统和/或浏览器中设置的默认字体大小的单位。当然,浏览器使用这些单位将值转换为像素,但这种方式对上下文和配置很敏感。相关单位为仲裁员。

The units em, rem, ch, and ex present no such problem because they are all units relative to the user’s default font size, as set in their operating system and/or browser. Browsers translate values using these units into pixels, of course, but in such a way that’s sensitive to context and configuration. Relative units are arbitrators.

相对论

Relativity

浏览器和操作系统通常只为用户提供调整基本主体字体大小的能力。这可以表示为1rem:恰好是根字体大小的一倍。您的段落元素应始终为1rem,因为它们代表正文。您不需要1rem明确设置,因为它是默认值。

Browsers and operating systems typically only afford users the ability to adapt the base or body font size. This can be expressed as 1rem: exactly one times the root font size. Your paragraph elements should always be 1rem, because they represent body text. You don’t need to set 1rem explicitly, because it’s the default value.

:root {
  /* ↓ redundant */
  font-size: 1rem;
}

p {
  /* ↓ also redundant */
  font-size: 1rem;
}

元素,如标题,应该设置得相对较大——否则层次结构将丢失。例如,我<h2>可能是2.5rem

Elements, like headings, should be set relatively larger — otherwise hierarchy will be lost. My <h2> might be 2.5rem, for example.

h2 {
  /* ↓ 2.5 × the root font-size */
  font-size: 2.5rem;
}

虽然单位emremchex都是文本的度量单位,但它们当然可以应用于marginpaddingborder属性(以及其他)。只是文本是网络媒体的基础,而这些单位是对这一事实的方便和不断的提醒。学习从文本的内在维度推断布局,您的设计将会很漂亮。

While the units em, rem, ch, and ex are all measurements of text, they can of course be applied to the margin, padding, and border properties (among others). It’s just that text is the basis of the web medium, and these units are a convenient and constant reminder of this fact. Learn to extrapolate your layouts from your text’s intrinsic dimensions and your designs will be beautiful.

比例性和可维护性

Proportionality and maintainability

<h2>的是根/基大小的 2.5 倍。如果我扩大根的大小,我的<h2>——以及所有其他以基倍数设置的维度——将按比例rem扩大。结果是缩放整个界面是微不足道的:

My <h2> is 2.5 times the root/base size. If I enlarge the root size, my <h2>—and all the other dimensions set in rem-based multiples—will by enlarged proportionately. The upshot is that scaling the entire interface is trivial:

@media (min-width: 960px) {
  :root {
    /* ↓ Upscale by 25% at 960px */
    font-size: 125%;
  }
}

如果我改为采用px,对维护的影响将很明显:缺乏相对和比例大小将需要逐个调整各个元素。

If I had instead adopted px, the implications for maintenance would be clear: the lack of relative and proportional sizing would require adjusting individual elements case-by-case.

h3 {
  font-size: 32px;
}
h2 {
  font-size: 40px;
}

@media (min-width: 960px) {
  h3 {
    font-size: 40px;
  }
  h2 {
    font-size: 48px;
  }
  /* etc etc ad nauseum */
}

视口单位

Viewport units

Every Layout中,我们避免基于宽度的@media查询。它们代表布局重新配置的硬编码,并且对实际提供给相关元素或组件的即时可用空间不敏感。在离散断点处缩放界面,如上一个示例所示,是任意的。有什么特别之处960px?我们真的可以说较小的尺寸是可以接受的959px吗?

In Every Layout, we eschew width-based @media queries. They represent the hard coding of layout reconfigurations, and are not sensitive to the immediate available space actually afforded the element or component in question. Scaling the interface at a discrete breakpoint, as in the last example, is arbitrary. What’s so special about 960px? Can we really say the smaller size is acceptable at 959px?

959px 版本的文字非常小,而 960px 版本(宽度仅相差一个像素)的文字非常大

The 959px version has very small text, and the 960px version (just one pixel different in width) has very large text

图片说明:1px差异表示使用断点时 的显着跳跃。

视口单位与浏览器视口的大小有关。例如,1vw等于屏幕宽度的1vh1%,等于屏幕高度的 1%。使用视口单位,calc()我们可以创建一种算法,其中尺寸按比例缩放,但从最小值开始。

Viewport units are relative to the browser viewport’s size. For example, 1vw is equal to 1% of the screen’s width, and 1vh is equal to 1% of the screen’s height. Using viewport units and calc() we can create an algorithm whereby dimensions are scaled proportionately, but from a minimum value.

:root {
  font-size: calc(1rem + 0.5vw);
}

等式的1rem一部分确保font-size永远不会低于默认(系统/浏览器/用户定义)值。也就是说,1rem + 0vw1rem

The 1rem part of the equation ensures the font-size never drops below the default (system/browser/user defined) value. That is, 1rem + 0vw is 1rem.

em单位_

The em unit

em单位之于单位rem就像容器查询之于@media查询。它与直接上下文相关,而不是外部文档。如果我想在我<strong>的 中稍微放大一个元素,我可以使用单位:font-size<h2>em

The em unit is to the rem unit what a container query is to a @media query. It pertains to the immediate context rather than the outer document. If I wanted to slightly enlarge a <strong> element’s font-size within my <h2>, I could use em units:

h2 {
  font-size: 2.5rem;
}

h2 strong {
  font-size: 1.125em;
}

<strong>'sfont-size现在1.125 × 2.5rem,或2.53125rem。如果我为它设置一个rem<strong>,它就不会随着它的父级缩放<h2>:如果我改变了这个h2值,我也必须改变h2 strongCSS 值。

The <strong>’s font-size is now 1.125 × 2.5rem, or 2.53125rem. If I set a rem value for the <strong> instead, it wouldn’t scale with its parent <h2>: if I changed the h2 value, I would have to change the h2 strong CSS value as well.

根据经验,em单位更适合调整行内元素的大小,而rem单位更适合块元素。SVG 图标是基于 - 调整大小的完美候选em,因为它们要么伴随文本,要么取代文本。

As a rule of thumb, em units are better for sizing inline elements, and rem units are better for block elements. SVG icons are perfect candidates for em-based sizing, since they either accompany or supplant text.

下载图标的高度为 0.75rem,以匹配随附文本的高度:Download Stack.zip

An download icon has a 0.75rem height to match the height of the accompanying text: Download Stack.zip

图片说明:在某些情况下,图标高度/宽度的实际值 inems必须适应随附字体自身的指标。本网站使用的 Barlow Condensed 字体有很多内部空间可以补偿——因此很有0.75rem价值。

chex单位_

The ch and ex units

ch和单位分别对应于一个字符的ex(近似)宽度和高度。1ch基于 a 的宽度,0等于1ex字体字符的高度——x也称为x 高度或语料库大小

The ch and ex units pertain to the (approximate) width and height of one character respectively. 1ch is based on the width of a 0, and 1ex is equal to the height of your font’s x character—also known as the x height or corpus size.

显示一个零的宽度为1ch,一个小写x的高度为一个ex

Shows that the width of one zero is 1ch and the height of one lowercase x is one ex

公理部分,ch单位用于限制元素的可读性度量。由于度量是每行字符数的问题,因此(字符ch的缩写)是此样式任务的唯一合适单位。

In the Axioms section, the ch unit is used to restrict elements’ measure for readability. Since measure is a question of characters per line, ch (short for character) is the only appropriate unit for this styling task.

An<h2>和 an<h3>可以具有不同font-size的值,但具有相同的(最大)度量。

An <h2> and an <h3> can have different font-size values, but the same (maximum) measure.

h2,
h3 {
  max-inline-size: 60ch;
}

h3 {
  font-size: 2rem;
}
h2 {
  font-size: 2.5rem;
}

一整行文本的宽度(以像素为单位)是根据-based和-based之间的关系推断出来的。通过委托一个算法来确定这个值——而不是将其硬编码为基于——我们避免了频繁和严重的错误。在 CSS 布局术语中,错误是格式错误或模糊的内容:人类数据丢失remfont-sizechmax-widthpxwidth

The width, in pixels, of one full line of text is extrapolated from the relationship between the rem-based font-size and ch-based max-width. By delegating an algorithm to determine this value—rather than hard coding it as a px-based width—we avoid frequent and serious error. In CSS layout terms, an error is malformed or obscured content: data loss for human beings.

全局和本地样式

Global and local styling

组合部分,我们介绍了如何使用用于布局的小型非词汇组件来创建更大的组合,但并非高效且一致的基于 CSS 的设计系统中的所有样式都应该严格基于组件。本节将在包含全局样式的更大系统中将布局组件置于上下文中。

In the Composition section we covered how small, nonlexical components for layout can be used to create larger composites, but not all styles within an efficient and consistent CSS-based design system should be strictly component based. This section will contextualize layout components in a larger system that includes global styles.

什么是全局样式?

What are global styles?

当人们谈论 CSS 的全局性时,他们可能指的是几种不同的事物之一。他们可能指的是全局继承:root的或<body>元素的规则(只有少数例外)。

When people talk about the global nature of CSS, they can mean one of a few different things. They may be referring to rules on the :root or <body> elements that are inherited globally (with just a few exceptions).

:root {
  /* ↓ Now (almost) all elements display a sans-serif font */
  font-family: sans-serif;
}

或者,它们可能意味着使用非限定*选择器直接为所有元素设置样式。

Alternatively, they may mean using the unqualified * selector to style all elements directly.

* {
  /* ↓ Now literally all elements display a sans-serif font */
  font-family: sans-serif;
}

元素选择器更具体,只针对它们命名的元素。但它们仍然是“全球性的”,因为无论它们位于何处,它们都可以接触到这些元素。

Element selectors are more specific, and only target the elements they name. But they are still “global” because they can reach those elements wherever they are situated.

p {
  /* ↓ Wherever you put a paragraph, it’ll be sans-serif */
  font-family: sans-serif;
}

元素选择器的自由使用是综合设计系统的标志。元素选择器负责通用原子,例如标题、段落、链接和按钮。与使用类时不同(见下文),元素选择器可以针对所见即所得编辑器markdown生成的任意、无属性的内容。

A liberal use of element selectors is the hallmark of a comprehensive design system. Element selectors take care of generic atoms such as headings, paragraphs, links, and buttons. Unlike when using classes (see below), element selectors can target the arbitrary, unattributed content produced by WYSIWYG editors and markdown.

Every Layout布局不会探索或规定简单元素的样式;那是你决定的。我们在这里感兴趣的是将简单元素嵌入到复合布局中。

The layouts of Every Layout do not explore or prescribe styles for simple elements; that is for you to decide. It is the imbrication of simple elements into composite layouts that we are interested in here.

一个标有布局的框包含三个框,每个标有元素或布局

A box labeled layout contains three boxes, each labeled element or layout

图片说明:每个布局都需要一个容器元素,该元素为其子元素建立格式化上下文。没有为其建立上下文的子元素的简单元素可以被视为布局层次结构中的“端节点”。

最后,一旦定义了基于类的样式,就可以将其附加到文档中任何位置的任何 HTML 元素。这些比元素样式更可移植和可组合,但需要作者直接影响标记。

Finally, class-based styles, once defined, can be adhered to any HTML element, anywhere in a document. These are more portable and composable than element styles, but require the author to affect the markup directly.

.sans-serif {
  font-family: sans-serif;
}
<div class="sans-serif">...</div>

<small class="sans-serif">...</small>

<h2 class="sans-serif">...</h2>

应该认识到利用 CSS 规则的全球影响力是多么重要。CSS 本身的存在是为了在全局范围内按类别启用 HTML 样式,而不是逐个元素。按预期使用时,它是在网络上创建任何类型的布局或美学的最有效方式。在适当使用全局样式技术(例如上面的技术)的情况下,将品牌/美学与布局分开,并将两者视为单独的关注点会容易得多。

It should be appreciated how important it is to leverage the global reach of CSS rules. CSS itself exists to enable the styling of HTML globally, and by category, rather than element-by-element. When used as intended, it is the most efficient way to create any kind of layout or aesthetic on the web. Where global styling techniques (such as the ones above) are used appropriately, it’s much easier to separate branding/aesthetic from layout, and treat the two as separate concerns.

两个独立的层。 顶层标有字体、颜色、阴影等。底层标有框的排列。

Two separate layers. The top layer is labeled fonts, colors, shadows, etc. The bottom layer is labeled arrangement of boxes.

实用类

Utility classes

正如我们已经说过的,类在可移植性方面不同于其他全局样式方法:您可以在不同的 HTML 元素及其类型之间使用类。这使我们可以全局地与继承的、通用的和元素样式发散

As we already stated, classes differ from the other global styling methods in terms of their portability: you can use classes between different HTML elements and their types. This allows us to diverge from inherited, universal, and element styles globally.

例如,<h2>默认情况下,我们所有的元素都可以设置样式2.25rem font-size

For example, all of our <h2> elements may be styled, by default, with a 2.25rem font-size:

h2 {
  font-size: 2.25rem;
}

h3 {
  font-size: 1.75rem;
}

但是,在某些特定情况下,我们可能希望font-size稍微减少它(可能水平空间非常宝贵,或者标题位于视觉可供性较低的地方)。如果我们要切换到一个<h3>元素来影响这种视觉变化,我们就会使文档结构变得毫无意义

However, there may be a specific cases where we want that font-size to be diminished slightly (perhaps horizontal space is at a premium, or the heading is somewhere where it should have less visual affordance). If we were to switch to an <h3> element to affect this visual change, we would make a nonsense of the document structure.

相反,我们可以构建一个与 smaller<h2>的上下文相关的更复杂的选择器:

Instead, we could build a more complex selector pertaining to the smaller <h2>’s context:

.sidebar h2 {
  font-size: 1.75rem;
}

虽然这比搞乱文档结构要好,但我犯了一个错误,没有考虑整个新兴系统:我们已经在特定上下文中解决了特定元素的问题,而我们应该解决一般问题任何上下文中的任何元素的问题(需要调整font-size) 。这就是实用程序类的用武之地。

While this is better than messing up the document structure, I've made the mistake of not taking the whole emerging system into consideration: We've solved the problem for a specific element, in a specific context, when we should be solving the general problem (needing to adjust font-size) for any element in any context. This is where utility classes come in.

/* ↓ Backslash to escape the colon */
.font-size\:base {
  font-size: 1rem;
}

.font-size\:biggish {
  font-size: 1.75rem;
}

.font-size\:big {
  font-size: 2.25rem;
}

我们使用了一个非常特别命名约定,它模拟了 CSS 声明结构:property-name:value. 这有助于回忆实用程序类名称,尤其是在值与实际值相呼应的地方,例如.text-align:center.

We use a very on the nose naming convention, which emulates CSS declaration structure: property-name:value. This helps with recollection of utility class names, especially where the value echos the actual value, like .text-align:center.

在元素和实用程序之间共享值是自定义属性的工作。请注意,我们通过将自定义属性附加到:root( <html>) 元素使它们本身在全球范围内可用:

Sharing values between elements and utilities is a job for custom properties. Note that we’ve made the custom properties themselves globally available by attaching them to the :root (<html>) element:

:root {
  --font-size-base: 1rem;
  --font-size-biggish: 1.75rem;
  --font-size-big: 2.25rem;
}

/* elements */

h3 {
  font-size: var(--font-size-biggish);
}
h2 {
  font-size: var(--font-size-big);
}

/* utilities */

.font-size\:base {
  font-size: var(--font-size-base) !important;
}

.font-size\:biggish {
  font-size: var(--font-size-biggish) !important;
}

.font-size\:big {
  font-size: var(--font-size-big) !important;
}

每个实用程序类都有一个!important后缀以最大限度地发挥其特殊性。实用程序类用于最终调整,不应被它们之前的任何内容覆盖。

Each utility class has an !important suffix to max out its specificity. Utility classes are for final adjustments, and should not be overridden by anything that comes before them.

一个倒三角形,触角指向顶部边缘,特异性指向底部点

An inverted triangle with reach pointing towards to top edge and specificity reaching towards to bottom point

图片说明:明智的 CSS 架构的“影响力”(有多少元素受到影响)与特异性(选择器的复杂程度)成反比。Harry Roberts 将其形式化为 ITCSS,其中 IT 代表倒三角。

前面示例中的值仅用于说明。为了整个设计的一致性,您的尺寸可能应该来自模块化比例。有关更多信息,请参见模块化秤

The values in the previous example are just for illustration. For consistency across the design, your sizes should probably be derived from a modular scale. See Modular scale for more.

本地或“作用域”样式

Local or 'scoped' styles

值得注意的是,id属性/特性(出于可访问性的原因,最重要的是)只能在每个文档的一个 HTML 元素上使用。因此,通过选择器的样式id仅限于一个实例。

Notably, the id attribute/property (for reasons of accessibility, most importantly) can only be used on one HTML element per document. Styling via the id selector is therefore limited to one instance.

#unique {
  /* ↓ Only styles id="unique" */
  font-family: sans-serif;
}

选择id器具有非常高的特异性,因为它假定独特的样式在所有情况下都旨在覆盖竞争的通用样式。

The id selector has a very high specificity because it’s assumed unique styles are intended to override competing generic styles in all cases.

当然,没有什么比使用属性/特性将样式直接应用于元素更“本地”或特定于实例的了:style

Of course, there’s nothing more “local” or instance specific than applying styles directly to elements using the style attribute/property:

<p style="font-family: sans-serif">...</p>

唯一剩下的本地化样式标准在 Shadow DOM 中。通过将元素设为 a shadowRoot,可以使用只影响该父元素内部元素的低特异性选择器。

The only remaining standard for localizing styles is within Shadow DOM. By making an element a shadowRoot, one can use low-specificity selectors that only affect elements inside that parent.

const elem = document.querySelector('div');
const shadowRoot = elem.attachShadow({mode: 'open'});
shadowRoot.innerHTML = `
  <style>
    p {
      /* ↓ Only styles <p>s inside the element’s Shadow DOM */
      font-family: sans-serif;
    }
  </style>
  <p>A sans-serif paragraph</p>
`;

缺点

Drawbacks

选择id器、内联样式和 Shadow DOM 都有缺点:

The id selector, inline styles, and Shadow DOM all have drawbacks:

  • id选择器:许多人发现高特异性会导致系统性问题。还有必要id在每种情况下提出 ' 的名称。动态生成唯一字符串通常更可取。
  • 内联样式:维护的噩梦,这就是 CSS 最初被反设计的原因。
  • Shadow DOM:不仅可以防止样式从 Shadow DOM 根目录“泄露”出来,而且(大多数)样式也不允许进入其中——这意味着您不能再利用全局样式。

外框代表 DOM,内框代表 Shadow DOM。 来自 DOM 的样式从 Shadow DOM 框反弹,Shadow DOM 内部的样式从它自己的边缘反弹并保持包含。

The outer box represents the DOM and the inner box represents the Shadow DOM. Styles from the DOM bounce off the Shadow DOM box, and styles inside the Shadow DOM bounce off its own edges and stay contained.

我们需要的是一种既能利用全局样式,又能以受控方式应用本地、特定于实例的样式的方法。

What we need is a way to simultaneously leverage global styling, but apply local, instance-specific styles in a controlled fashion.

基元和道具

Primitives and props

正如Composition中所述, Every Layout的主要焦点在于有助于将元素/框排列在一起的简单布局原语。这些是用于引发布局的主要工具,并在通用全局样式和实用程序之间占据一席之地。

As set out in Composition, the main focus of Every Layout is on the simple layout primitives that help to arrange elements/boxes together. These are the primary tools for eliciting layout and take their place between generic global styles and utilities.

  1. 通用(包括继承)样式
  2. 布局原语
  3. 实用类

表现为可重用组件,使用自定义元素规范,这些布局可以在全球范围内使用。但是使用道具(属性)可以对这些布局进行独特的配置。

Manifested as reusable components, using the custom elements specification, these layouts can be used globally. But unique configurations of these layouts are possible using props (properties).

默认值

Defaults

每个布局组件都有一个随附的样式表,用于定义其基本样式和默认样式。例如,Stack样式表 ( Stack.css) 如下所示。

Each layout component has an accompanying stylesheet that defines its basic and default styles. For example, the Stack stylesheet (Stack.css) looks like the following.

stack-l {
  display: block;
}

stack-l > * + * {
  margin-top: var(--s1);
}

有几点需要注意:

A few things to note:

  • display: block声明是必需的,因为自定义元素默认呈现为内联元素。有关块和内联元素行为的更多信息,请参见
  • margin-top值是使Stack成为堆栈的原因:它在元素的垂直堆栈之间插入边距。默认情况下,该边距值与我们的模块化比例尺上的第一个点相匹配:--s1
  • 选择*器适用于任何元素,但我们的使用*限定匹配 a 的任何连续子元素<stack-l>(注意相邻的兄弟组合器)。布局原语是抽象的组合,不应该规定内容,所以我用它*来匹配给它的任何子元素类型。

较大的框标有布局。 它里面的三个框每个都标有星号(通用选择器)和文本“任何元素”。

The larger box is labeled layout. The three boxes inside it are each labeled with an asterisk (universal selector) and the text “any element”.

在自定义元素定义本身中,我们将默认值应用于space属性:

In the custom element definition itself, we apply the default value to the space property:

get space() {
  return this.getAttribute('space') || 'var(--s1)';
}

每个Every Layout自定义元素都会根据实例的属性值构建一个嵌入式样式表。也就是说,这个……

Each Every Layout custom element builds an embedded stylesheet based on the instance’s property values. That is, this…

<stack-l space="var(--s3)">
  <div>...</div>
  <div>...</div>
  <div>...</div>
</stack-l>

……会变成这样……

…would become this…

<stack-l data-i="Stack-var(--s3)" space="var(--s3)">
  <div>...</div>
  <div>...</div>
  <div>...</div>
</stack-l>

......并会产生这个:

… and would generate this:

<style id="Stack-var(--s3)">
  [data-i='Stack-var(--s3)'] > * + * {
    margin-top: var(--s3);
  }
</style>

然而——这部分很重要——该Stack-var(--s3)字符串仅表示布局的唯一配置,而不是唯一实例。一个id="Stack-var(--s3)" <style>元素用于服务<stack-l>共享该Stack-var(--s3)字符串表示的配置的所有实例。在相同配置的实例之间,唯一真正区分它们的是它们的内容

However—and this part is important—the Stack-var(--s3) string only represents a unique configuration for a layout, not a unique instance. One id="Stack-var(--s3)" <style> element is used to serve all instances of <stack-l> sharing the configuration represented by the Stack-var(--s3) string. Between instances of the same configuration, the only thing that really differentiates them is their content.

虽然网页中的每一项内容通常都应该提供独特的信息,但外观和感觉应该是一致的和规则的,使用熟悉和重复的模式、主题和安排。利用全局样式和受控的布局配置可以实现一致性和内聚性,并且代码最少。

While each item of content within a web page should generally offer unique information, the look and feel should be consistent and regular, using familiar and repeated patterns, motifs, and arrangements. Leveraging global styles along with controlled layout configurations results in consistency and cohesion, and with minimal code.

模块化秤

Modular scale

音乐基本上是一种数学阐述,当我们谈论排版的音乐性时,是因为排版和音乐共享一个数学基础。

Music is fundamentally a mathematical exposition, and when we talk about the musicality of typesetting it is because typesetting and music share a mathematical basis.

我们相信您一定听说过频率、音高和和声等概念。这些都是数学上可以确定的,但你知道感知音高可以由多个频率组成吗?

We’re sure you will have heard of concepts like frequency, pitch, and harmony. These are all mathematically determinable, but were you aware that perceived pitch can be formed of multiple frequencies?

一个单一的音符,例如通过拨动吉他弦产生的音符,本身就是一个乐曲。不同的频率(或谐波)一起属于谐波系列。调和级数是基于递增 1 的算术级数的分数序列。

A single musical note, such as one produced by plucking a guitar string, is in itself a composition. The different frequencies (or harmonics) together belong to a harmonic series. A harmonic series is a sequence of fractions based on the arithmetic series of incrementation by 1.

1,2,3,4,5,6 // arithmetic series
1,½,⅓,¼,⅕,⅙ // harmonic series

和谐频率的表示创造了对称的视觉形式

Representation of harmonious frequencies creating a symmetrical visual form

由于其规律性,由此产生的声音是和谐的。基频可被每个谐波频率整除,每个谐波频率是其两侧频率的平均值。

The resulting sound is harmonious because of its regularity. The fundamental frequency is divisible by each of the harmonic frequencies, and each harmonic frequency is the mean of the frequencies either side of it.

视觉和谐

Visual harmony

我们也应该以视觉布局的和谐为目标。就像弹拨弦的声音一样,它应该是连贯的。鉴于我们主要处理文本,将line-height视为推断空白值的基础是明智的。字体大小(隐含地)1remline-height1.5 创建默认值1.5rem. 和谐较大的空间可能是3rem(2 ⨉ 1.5) 或4.5rem(3 ⨉ 1.5)。

We should aim for harmony in our visual layout too. Like the sound of a plucked string, it should be cohesive. Given we’re working predominantly with text, it’s sensible to treat the line-height as a basis for extrapolating values for white space. A font-size of (implicitly) 1rem, and a line-height of 1.5 creates a default value of 1.5rem. A harmoniously larger space might be 3rem (2 ⨉ 1.5) or 4.5rem (3 ⨉ 1.5).

通过在每个步骤中添加 1.5 来创建序列会导致较大的间隔。相反,我们可以乘以 1.5。结果还是正常的;增量更小。

Creating a sequence by adding 1.5 at each step results in large intervals. Instead, we can multiply by 1.5. The result is still regular; the increments just smaller.

1 * 1.5; // 1.5
1.5 * 1.5; // 2.25
1.5 * 1.5 * 1.5; // 3.375

该算法称为模块化音阶,就像音阶一样旨在产生和声。如何在设计中使用它取决于您使用的技术。

This algorithm is called a modular scale, and like a musical scale is intended for producing harmony. How you employ it in your design depends on what technology you are using.

自定义属性

Custom properties

在 CSS 中,您可以使用自定义属性和calc()支持简单算术的函数来描述模块化比例尺。

In CSS, you can describe a modular scale using custom properties and the calc() function, which supports simple arithmetic.

在以下示例中,我们除以或乘以设置的--ratio自定义属性(变量)以在我们的刻度上创建点。我们可以利用已经设置的点来生成新的点。也就是说,var(--s2) * var(--ratio)相当于var(--ratio) * var(--ratio) * var(--ratio).

In the following example, we divide or multiply by the set --ratio custom property (variable) to create the points on our scale. We can make use of already set points to generate new ones. That is, var(--s2) * var(--ratio) is equivalent to var(--ratio) * var(--ratio) * var(--ratio).

:root {
  --ratio: 1.5;
  --s-5: calc(var(--s-4) / var(--ratio));
  --s-4: calc(var(--s-3) / var(--ratio));
  --s-3: calc(var(--s-2) / var(--ratio));
  --s-2: calc(var(--s-1) / var(--ratio));
  --s-1: calc(var(--s0) / var(--ratio));
  --s0: 1rem;
  --s1: calc(var(--s0) * var(--ratio));
  --s2: calc(var(--s1) * var(--ratio));
  --s3: calc(var(--s2) * var(--ratio));
  --s4: calc(var(--s3) * var(--ratio));
  --s5: calc(var(--s4) * var(--ratio));
}

使用 1.5 的系数增加大小的正方形彼此相邻放置。 一条曲线连接它们的左上角

Squares of increasing size, using a factor of 1.5, are placed next to each other. A curved line connects their top left corners

图片说明:注意当连接代表刻度上的点的正方形的左上角时可观察到的弯曲倾斜

JavaScript 访问

JavaScript access

我们的比例变量放置在:root元素上,使它们在全球范围内可用。所谓全球化,我们指的是真正的全球化。自定义属性可用于 JavaScript,也可“穿透”Shadow DOM 边界以影响样式表的 CSS shadowRoot

Our scale variables are placed on the :root element, making them globally available. And by global, we mean truly global. Custom properties are available to JavaScript and also “pierce” Shadow DOM boundaries to affect the CSS of a shadowRoot stylesheet.

JavaScript 使用 JSON 属性等 CSS 自定义属性。您可以将全局自定义属性视为 CSS 和 JavaScript 共享的配置。以下是您如何--s3使用 JavaScript(document.documentElement代表:root, 或<html>元素)在刻度上取得分数:

JavaScript consumes CSS custom properties like JSON properties. You can think of global custom properties as configurations shared by CSS and JavaScript. Here’s how you would get the --s3 point on the scale using JavaScript (document.documentElement represents the :root, or <html> element):

const rootStyles = getComputedStyle(document.documentElement);
const scale3 = rootStyles.getPropertyValue('--s3');

影子 DOM 支持

Shadow DOM support

在 Shadow DOM 中调用时会成功应用相同的--s3属性,如以下示例所示。:host选择器指的是假设的自定义元素本身。

The same --s3 property is successfully applied when invoked in Shadow DOM, as in the following example. The :host selector refers to the hypothetical custom element itself.

this.shadowRoot.innerHTML = `
  <style>
    :host {
      padding: var(--s3);
    }
  </style>
  <slot></slot>
`;

通过 props 传递

Passing via props

有时我们可能希望我们的自定义元素使用属性 ( props ) 中的某些样式——在本例中为paddingprop。

Sometimes we might want our custom element to consume certain styles from properties (props) — in this case a padding prop.

<my-element padding="var(--s3)">
  <!-- Light DOM contents -->
</my-element>

可以使用模板文字var(--s3)字符串插入到自定义元素实例的 CSS 中:

The var(--s3) string can be interpolated into the custom element instance's CSS using a template literal:

this.shadowRoot.innerHTML = `
  <style>
    :host {
      padding: ${this.padding};
    }
  </style>
  <slot></slot>
`;

但首先我们需要为我们的padding道具编写一个 getter 和一个 setter。|| var(--s1)getterreturn行中的后缀是默认值。使用合理的默认值可以减少布局组件的工作量;我们的目标是约定优于配置

But first we need to write a getter and a setter for our padding prop. The || var(--s1) suffix in the getter’s return line is the default value. Use of sensible defaults makes working with layout components less laborious; we’re aiming for convention over configuration.

  get padding() {
    return this.getAttribute('padding') || 'var(--s1)';
  }

  set padding(val) {
    return this.setAttribute('padding', val);
  }

加强一致性

Enforcing consistency

padding道具目前是允许的;作者可以提供一个自定义属性,或一个简单的长度值,如1.25rem. 如果我们想强制使用我们的模块化比例尺,我们将只接受数字 ( 2, 3, -1) 并像 一样对它们进行插值var(--${this.padding})

This padding prop is currently permissive; the author can supply a custom property, or a simple length value like 1.25rem. If we wanted to enforce the use of our modular scale, we would accept only numbers (2, 3, -1) and interpolate them like var(--${this.padding}).

我们可以使用正则表达式检查是否传递了一个整数值。HTML 属性值是隐式字符串。我们正在寻找包含数字的单个数字字符串。

We could check that an integer value is being passed using a regular expression. HTML attribute values are implicitly strings. We are looking for a single digit string containing a number.

if (!/(?<!\S)\d(?!\S)/.test(this.padding)) {
  console.error('<my-component>’s padding value should be a number representing a point on the modular scale');
  return;
}

在这种情况下,模块化比例基于单个数字1.5。通过外推——作为乘数和除数——数字的存在可以在整个视觉设计中感受到。一致、平衡的设计源于简单的公理,如模块化比例。

The modular scale is predicated on a single number, in this case 1.5. Through extrapolation—as a multiplier and divisor—the number’s presence can be felt throughout the visual design. Consistent, balanced design is seeded by simple axioms like the modular scale ratio.

一些人认为用于一个人的模块化比例的特定比例很重要,许多人坚持黄金1.61803398875比例。但只有严格遵守您选择的任何比例,才能创造出和谐。

Some believe the specific ratio used for one’s modular scale is important, with many adhering to the golden ratio of 1.61803398875. But it is in the strict adherence to whichever ratio you choose that harmony is created.

公理

Axioms

正如数学家欧几里得所知,即使是最复杂的几何也是建立在简单的、不可简化的公理(或公设之上的。除非你的设计是建立在公理之上的,否则你的输出将是不一致和畸形的。本节的主题是如何在系统范围内遵守设计公理,以排版度量为范例。

As the mathematician Euclid was aware, even the most complex geometries are founded on simple, irreducible axioms (or postulates). Unless your design is founded on axioms, your output will be inconsistent and malformed. The subject of this section is how to honor a design axiom system-wide, using typographic measure as an exemplar.

措施

Measure

一行文本的宽度(以字符为单位)称为其度量值。选择合理的措施对于舒适地扫描连续线条至关重要。The Elements Of Typographic Style认为 45 到 75 之间的任何值都是合理的。

The width of a line of text, in characters, is known as its measure. Choosing a reasonable measure is critical for the comfortable scanning of successive lines. The Elements Of Typographic Style considers any value between 45 and 75 reasonable.

为印刷媒体设置衡量标准相对简单。它只是纸制品的宽度除以放置在其中的文本栏的数量——当然减去边距和间距

Setting a measure for print media is relatively straightforward. It is simply the width of the paper artefact divided by the number of text columns placed within it — minus margins and gutters, of course.

一个完整的杂志布局,分为三列文本

A full-spread magazine layout divided into three columns of text

网络不像印刷品那样是静态的或可预测的。每个单词由一个分隔符分隔(unicode 点 U+0020),释放文本运行以根据可用空间动态换行。可用空间量由许多相互关联的因素决定,包括设备大小和方向、文本大小和缩放级别。

The web is not static or predictable like print. Each word is separated by a breaking space (unicode point U+0020), freeing runs of text to wrap dynamically according to available space. The amount of available space is determined by a number of interrelated factors including device size and orientation, text size, and zoom level.

作为设计师,我们寻求控制用户的体验。但是,正如 John Allsopp 在 2000 年的The Dao Of Web Design中所写,试图直接控制用户在网络上消费内容的方式是鲁莽的。执行特定措施意味着设置固定宽度。许多用户会遇到水平滚动和损坏的缩放功能。

As designers, we seek to control the users’ experience. But, as John Allsopp wrote in 2000’s The Dao Of Web Design, attempting direct control over the way users consume content on the web is foolhardy. Enforcing a specific measure would mean setting a fixed width. Many users would experience horizontal scrolling and broken zoom functionality.

当视口变窄时,文本保持相同的宽度并变得模糊。 出现水平滚动条

When the viewport is narrowed, the text stays the same width and becomes obscured. A horizontal scroll bar appears

要设计“自适应页面”(Allsopp 的术语),我们必须放弃对浏览器用于自动布置网页的算法(如文本换行)的控制。但这并不是说没有影响布局的地方。将自己视为浏览器的导师,而不是它的微观管理者。

To design “adaptable pages” (Allsopp’s term), we must relinquish control to the algorithms (like text wrapping) browsers use to lay out web pages automatically. But that’s not to say there’s no place for influencing layout. Think of yourself as the browser’s mentor, rather than its micro-manager.

测度公理

The measure axiom

尝试用简短的短语或句子列出设计公理是一种很好的做法。在这种情况下,该语句可能是“该度量不应超过 60ch”

It’s good practice to try and set out a design axiom in a short phrase or sentence. In this case that statement might be, “the measure should never exceed 60ch”.

衡量什么?在哪里?任何一行文本都没有理由变得太长。与所有公理一样,这个公理应该无条件或例外地遍及设计。真正的问题是:如何?在全局和本地样式中,我们列出了三个主要的样式层:

The measure of what? And where? There’s no reason why any line of text should become too lengthy. This axiom, like all axioms, should pervade the design without qualifications or exceptions. The real question is: how? In Global and local styling we set out three main tiers of styling:

  1. 通用(包括继承)样式
  2. 布局原语
  3. 实用类

度量公理应尽可能普遍地植入通用样式中,但也可用于布局基元(请参阅组合)和实用程序类。但首先,哪个属性和哪个值应该写入规则?

The measure axiom should be seeded as pervasively as possible in the universal styles, but also made available to layout primitives (see Composition) and utility classes. But first, which property and which value should inscribe the rule?

声明书

The declaration

固定宽度(和高度!)是响应式设计的诅咒,正如我们在Boxes和此处再次建立的那样。相反,我们应该处理公差。例如,该max-inline-size属性在任何书写模式下都可以容忍任何长度的文本,最高可达特定值。

Fixed widths (and heights!) are anathema to responsive design, as we established in Boxes and again here. Instead, we should deal in tolerances. The max-inline-size property, for example, tolerates any length of text, in any writing mode, up to a certain value.

p {
  max-inline-size: 700px;
}

这就是所涵盖的财产。但是,px单位有问题。我们也许能够通过肉眼判断,这700px为给定的 创造了一个合理的衡量标准font-size。但给定font-size的实际上只是font-size我们当时恰好显示的屏幕——这是我们对自己设计的狭隘看法。

That’s the property covered. However, the px unit is problematic. We may be able to judge, by eye, that 700px creates a reasonable measure for the given font-size. But the given font-size is really just the font-size our screen happens to be displaying at the time — it’s our parochial view of our own design.

更改font-size段落或调整默认系统字体大小,将创建不同的(最大)度量。因为字符长度和像素宽度之间没有关系,所以我们没有算法可以保证正确的最大度量值。

Changing font-size for paragraphs, or adjusting the default system font size, will create a different (maximum) measure. Because there is no relationship between character length and pixel width, we do not have an algorithm that can guarantee the correct maximum measure value.

无论度量如何,基于像素的度量都会创建等宽的文本列

A pixel based measure creates equal width columns of text regardless of measure

幸运的是,CSS 包括ch单位。的值1ch基于字体0字符的宽度。重要的是,这意味着改变font-size的值1ch,从而调整度量。使用ch单位是一种天生的算法测量方法,因为结果取决于您允许浏览器为您进行的计算。

Fortunately, CSS includes the ch unit. The value of 1ch is based on the width of the font’s 0 character. Importantly, this means changing the font-size changes the value of 1ch, thereby adapting the measure. Using ch units is an innately algorithmic approach to measure, because the outcome is predicated on a calculation you permit the browser to make for you.

使用ch使我们能够独立于执行公理font-size,使其高度普遍并且没有“出错”的危险。在某些文档中, “测量不应超过 60ch”可能是注释,它可以是直接编码到设计特征中的质量。

Using ch enables us to enforce the axiom independent of font-size, allowing it to be highly pervasive and in no danger of “going wrong”. Where "the measure should never exceed 60ch" might have been a note in some documentation, it can instead be a quality directly coded into the design’s character.

全局默认值

Global defaults

要实现这个公理,我们需要确保所有适用的元素都服从它。这是选择器的问题。我们可以创建一个类选择器……

To realize the axiom, we need to ensure all applicable elements are subject to it. This is a question of selectors. We could create a class selector…

.measure-cap {
  max-inline-size: 60ch;
}

…但是过早地从(实用程序)类的角度思考是错误的。这意味着手动将样式应用于 HTML 中的各个元素,只要我们认为它适用。手动干预很费力,容易出错(缺少元素),并且会导致标记过大。

…but it’s a mistake to think in terms of (utility) classes too early. It would mean applying the style manually, to individual elements in the HTML, wherever we felt it was applicable. Manual intervention is laborious, prone to error (missing elements out), and will lead to bloated markup.

相反,我们应该问自己规则可能适用于哪些类型的元素。当然是为文本设计的流元素。<em>像和这样的内联元素<small>不需要包含在内,因为它们只会占用其父流元素总度量的一部分。

Instead, we should ask ourselves which types of elements the rule might apply to. Certainly flow elements designed for text. Inline elements like <em> and <small> would not need to be included, since they would take up only a part of their parent flow elements’ total measure.

p,
h1,
h2,
h3,
h4,
h5,
h6,
li,
figcaption {
  max-inline-size: 60ch;
}

基于异常的样式

Exception-based styling

很难知道我们是否记得这里的一切。基于异常的方法更聪明,因为我们只需要记住哪些元素不应规则约束。请注意,内联元素包含在以下示例中,但由于它们占用的水平空间与其父级相等或更小,因此不会出现不良影响。

It’s difficult to know if we’ve remembered everything here. An exception based approach is smarter, since we only have to remember which elements should not be subject to the rule. Note that inline elements would be included in the following example but, since they would take up an equal or lesser horizontal space than their parents, no ill effects would emerge.

* {
  max-inline-size: 60ch;
}

html,
body,
div,
header,
nav,
main,
footer {
  max-inline-size: none;
}

<div>元素特别倾向于用作通用容器/包装器。这些元素中的一些可能包含多个相邻的盒子,每个盒子中的一个或多个希望占据整个60ch. 这使他们的父母成为合乎逻辑的例外。

The <div> element particularly tends to be used as a generic container/wrapper. It’s likely some of these elements will contain multiple adjacent boxes, with one or more of each wishing to take up the full 60ch. This makes their parents logical exceptions.

基于异常的 CSS 方法可以让我们用最少的代码完成大部分的样式设置。如果您没有采用基于例外的方法,可能是因为制造例外感觉就像改正错误。但事实远非如此。具有层叠和其他特性的 CSS就是为此而设计的。在 Harry Roberts 的ITCSS(Inverted Triangle CSS)论文中,specificity(选择器的具体程度如何)与 reach(它们应该影响多少元素)成反比。

An exception-based approach to CSS lets us do most of our styling with the least of our code. If you are not taking an exception-based approach, it may be because making exceptions feels like correcting mistakes. But this is far from the case. CSS, with its cascade and other features, is designed for this. In Harry Roberts’ ITCSS (Inverted Triangle CSS) thesis, specificity (how specific selectors are) is inversely proportional to reach (how many elements they should affect).

普世价值

A universal value

在开始到处使用度量值之前,我们最好将其定义为自定义属性。这样,对值的任何更改都将在整个设计中传播。

Before we start using the measure value everywhere, we’d best define it as a custom property. That way, any change to the value will be propagated throughout the design.

请注意,并非所有自定义属性都必须是全局的,但在这种情况下,我们希望我们的元素、道具和实用程序类一致。因此,我们将自定义属性放在:root元素上。

Note that not all custom properties have to be global, but in this case we want our elements, props, and utility classes to agree. Therefore, we place the custom property on the :root element.

:root {
  --measure: 60ch;
}

这被传递到我们的通用块中......

This is passed into our universal block…

* {
  max-inline-size: var(--measure);
}

html,
body,
div,
header,
nav,
main,
footer {
  max-inline-size: none;
}

......以及我们可能发现我们需要的任何实用程序类。

…and to any utility classes we may find we need.

.max-inline-size\:measure {
  max-inline-size: var(--measure);
}

.max-inline-size\:measure\/2 {
  max-inline-size: calc(var(--measure) / 2);
}

在复合布局中测量

Measure in composite layouts

某些布局原语不可避免地接受与测量相关的道具,并且一些使用var(--measure). Switcher有一个thresholdprop 定义布局在水平和垂直配置之间切换的容器宽度:

Certain layout primitives inevitably accept measure-related props, and some set default values for those props using var(--measure). The Switcher has a threshold prop that defines the container width at which the layout switches between a horizontal and vertical configuration:

get threshold() {
  return this.getAttribute('threshold') || 'var(--measure)';
}

set threshold(val) {
  return this.setAttribute('threshold', val);
}

这是一个合理的默认值,但可以很容易地用任何字符串值覆盖:

This is a sensible default, but can easily be overridden with any string value:

<switcher-l threshold="20rem">...</switcher-l>

如果我们将非法值传递给threshold,声明将被删除,切换器的后备样式表将应用默认值。该样式表如下所示:

If we pass an illegitimate value to threshold, the declaration will be dropped, and the Switcher’s fallback stylesheet will apply the default value anyway. Here’s what that stylesheet looks like:

switcher-l {
  display: flex;
  flex-wrap: wrap;
}

switcher-l > * {
  flex-basis: calc((var(--measure) - 100%) * 999);
  flex-grow: 1;
}

我们的衡量方法是我们假设控制的方法,但是一种对浏览器的工作方式和用户操作它们的方式尊重的缓和控制。许多管理您的设计的“公理”,例如“正文字体将是 Font X”“标题将是深蓝色”不会对布局本身产生影响,从而使它们更易于仅使用全局样式来应用。当布局进入等式时,请注意不同的配置和方向。选择使浏览器能够代表您计算最合适的布局的属性、值和单位。

Our approach to measure is one where we assume control, but a tempered kind of control that's deferential towards the way browsers work and users operate them. Many of the 'axioms' that govern your design, like "the body font will be Font X" or "headings will be dark blue" will not have an impact on layout as such, making them much simpler to apply just with global styles. When layout comes into the equation, be wary of differing configurations and orientations. Choose properties, values, and units that enable the browser to calculate the most suitable layout on your behalf.

堆栈

The Stack

问题

The problem

流元素需要空间(有时称为空白)以在物理上和概念上将它们与它们之前和之后的元素分开。这就是margin财产的目的。

Flow elements require space (sometimes referred to as white space) to physically and conceptually separate them from the elements that come before and after them. This is the purpose of the margin property.

然而,设计系统孤立地构思元素和组件。在构思的时候,还没有确定是否会有周围的内容,或者那个内容的性质是什么。一个元素或组件可能会出现在不同的上下文中,对间距的要求也会有所不同。

However, design systems conceive elements and components in isolation. At the time of conception, it is not settled whether there will be surrounding content or what the nature of that content will be. One element or component is likely to appear in different contexts, and the requirement for spacing will differ.

我们习惯于直接对元素或元素类进行样式化:我们使样式声明属于元素。通常,这不会产生任何问题,但margin实际上是两个邻近元素之间关系的一个属性。因此下面的代码是有问题的:

We are in the habit of styling elements, or classes of elements, directly: we make style declarations belong to elements. Typically, this does not produce any issues, but margin is really a property of the relationship between two proximate elements. The following code is therefore problematic:

p {
  margin-bottom: 1.5rem;
}

由于声明对上下文不敏感,因此任何正确应用边距都是运气问题。如果该段落前面有另一个元素,则效果是理想的。但是一个:last-child段落会产生多余的边距。在填充的父元素内,这个冗余边距与父元素的填充相结合,产生双倍的预期空间。这只是这种方法产生的问题之一。

Since the declaration is not context sensitive, any correct application of the margin is a matter of luck. If the paragraph is proceeded by another element, the effect is desirable. But a :last-child paragraph produces a redundant margin. Inside a padded parent element, this redundant margin combines with the parent’s padding to produce double the intended space. This is just one problem this approach produces.

左边的例子显示了两个段落之间的预期空间。 右边的例子显示了底部段落和包含框的底部边缘/边框之间不需要的双倍空间

The left example shows an expected space between two paragraphs. The right example shows an undesired double space between the bottom paragraph and the bottom edge/border of the containing box

解决方案

The solution

诀窍是为上下文设置样式,而不是单个元素。Stack布局原语通过它们的共同父元素在元素之间注入边距:

The trick is to style the context, not the individual element(s). The Stack layout primitive injects margin between elements via their common parent:

.stack > * + * {
  margin-block-start: 1.5rem;
}

使用相邻兄弟组合符 ( +),margin-block-start仅适用于元素前面有另一个元素的情况:没有“剩余”边距。通用(或通配符)选择器 ( *) 确保所有元素都受到影响。关键* + *构造称为owl

Using the adjacent sibling combinator (+), margin-block-start is only applied where the element is preceded by another element: no “left over” margin. The universal (or wildcard) selector (*) ensures any and all elements are affected. The key * + * construct is known as the owl.

递归

Recursion

在前面的示例中,子组合器 ( >) 确保边距仅适用于元素的子.stack元素。但是,可以通过从选择器中删除此组合器来递归地注入边距。

In the previous example, the child combinator (>) ensures the margins only apply to children of the .stack element. However, it’s possible to inject margins recursively by removing this combinator from the selector.

.stack * + * {
  margin-block-start: 1.5rem;
}

如果您想要影响任何嵌套级别的元素,同时保留空白规则,这可能很有用。

This can be useful where you want to affect elements at any nesting level, while retaining white space regularity.

两个带边框的嵌套框不会显示双倍间距。 每个元素之间的空间相等

Two nested boxes with borders do not exhibit doubled spacing. The space is equal between each element

在下面的演示中(使用Stack 组件跟随)有一组盒子形状的元素。其中两个嵌套在另一个中。因为应用了递归,所以每个框仅使用一个父Stack均匀分布。

In the following demonstration (using the Stack component to follow) there are a set of box-shaped elements. Two of these are nested within another. Because recursion is applied, each box is evenly spaced using just one parent Stack.

此交互式演示仅在Every Layout站点上可用。

This interactive demo is only available on the Every Layout site.

您可能会发现递归模式会影响不需要的元素。例如,通常不被边距分隔的通用列表项会意外地散开

You’re likely to find the recursive mode affects unwanted elements. For example, generic list items that are typically not separated by margins will become unexpectedly spread out.

嵌套变体

Nested variants

无论嵌套深度如何,递归都应用相同的边距。一种更慎重的方法是设置具有不同边距值的替代非递归堆栈,并在合适的地方嵌套它们。考虑以下。

Recursion applies the same margin no matter the nesting depth. A more deliberate approach would be to set up alternative non-recursive Stacks with different margin values, and nest them where suitable. Consider the following.

[class^='stack'] > * {
  /* top and bottom margins in horizontal-tb writing mode */
  margin-block: 0;
}

.stack-large > * + * {
  margin-block-start: 3rem;
}

.stack-small > * + * {
  margin-block-end: 0.5rem;
}

此交互式演示仅在Every Layout站点上可用。

This interactive demo is only available on the Every Layout site.

第一个声明块的选择器重置所有Stack类元素的垂直边距(通过匹配以开头的类值stack)。重要的是,只有垂直边距被重置,因为堆栈只影响垂直边距,我们不希望它超出其范围。margin如果通用重置已经到位,您可能不需要此重置(请参阅全局和本地样式)。

The first declaration block’s selector resets the vertical margin for all Stack-like elements (by matching class values that begin with stack). Importantly, only the vertical margins are reset, because the stack only affects vertical margin, and we don't want it to reach outside its remit. You may not need this reset if a universal reset for margin is already in place (see Global and local styling).

以下两个块设置了替代Stacks,具有不同的边距值。这些可以嵌套以产生——例如——图示的表单布局。请注意,<label>需要display: block应用这些元素才能显示在输入上方,并且它们的边距实际上会产生空格(内联元素的垂直边距没有效果;请参阅显示属性)。

The following two blocks set up alternative Stacks, with different margin values. These can be nested to produce—for example—the illustrated form layout. Be aware that the <label> elements would need to have display: block applied to appear above the inputs, and for their margins to actually produce spaces (the vertical margin of inline elements has no effect; see The display property).

表单使用大 Stack 间距来分隔整个字段,并使用嵌套的小 Stack 间距将字段标签与其输入和错误分开

A form uses the large Stack spacing to separate whole fields, and a nested small Stack spacing to separate field labels from their inputs and errors

Every Layout中,自定义元素用于实现布局组件/原语,例如Stack。在Stack组件spaceprop(属性;属性)用于定义间距值。上面修改类的例子只是为了说明。请参阅嵌套示例

In Every Layout, custom elements are used to implement layout components/primitives like the Stack. In the Stack component, the space prop (property; attribute) is used to define the spacing value. The modified classes example above is just for illustration. See the nested example.

例外情况

Exceptions

CSS 最适合作为一种基于异常的语言。您编写影响深远的规则,然后在特殊情况下使用级联覆盖这些规则。正如在使用 CSS 自定义属性管理流程和节奏中所写,您可以在单个堆栈上下文中(即在相同的嵌套级别)创建每个元素的异常。

CSS works best as an exception-based language. You write far-reaching rules, then use the cascade to override these rules in special cases. As written in Managing Flow and Rhythm with CSS Custom Properties, you can create per-element exceptions within a single Stack context (i.e. at the same nesting level).

.stack > * + * {
  margin-block-start: var(--space, 1.5em);
}

.stack-exception,
.stack-exception + * {
  --space: 3rem;
}

请注意,我们在元素上方下方应用增加的间距.exception(如果适用)。如果您只想增加上面的空间,您可以删除.exception + *.

Note that we are applying the increased spacing above and below the .exception element, where applicable. If you only wanted to increase the space above, you would remove .exception + *.

*之所以可行,是因为特异性为零,因此.stack > * + *和在级联.stack-exception中具有相同的特异性和.stack-exception覆盖.stack > * + *(通过在样式表的更下方出现)。

This works because * has zero specificity, so .stack > * + * and .stack-exception are the same specificity and .stack-exception overrides .stack > * + * in the cascade (by appearing further down in the stylesheet).

拆分堆栈

Splitting the stack

通过使Stack成为一个 Flexbox 上下文,我们可以赋予它最后一个功能:为auto所选元素添加边距的能力。这样,我们可以将元素分组到垂直空间的顶部和底部。对于类似卡片的组件很有用。

By making the Stack a Flexbox context, we can give it one final power: the ability to add an auto margin to a chosen element. This way, we can group elements to the top and bottom of the vertical space. Useful for card-like components.

在下面的示例中,我们选择将元素分组到空间底部的第二个元素之后。

In the following example, we've chosen to group elements after the second element towards the bottom of the space.

.stack {
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
}

.stack > * + * {
  margin-block-start: var(--space, 1.5rem);
}

.stack > :nth-child(2) {
  margin-block-end: auto;
}

这可以在以下描述演示文稿/幻灯片编辑器的演示中的上下文中看到。右侧的Cover66.666vh元素的最小高度为,强制左侧边栏的高度高于其内容。这就是在幻灯片图像和“添加幻灯片”按钮之间产生间隙的原因。

This can be seen working in context in the following demo depicting a presentation/slides editor. The Cover element on the right has a minimum height of 66.666vh, forcing the left sidebar's height to be taller than its content. This is what produces the gap between the slide images and the "Add slide" button.

此交互式演示仅在Every Layout站点上可用。

This interactive demo is only available on the Every Layout site.

Stack是其父级的唯一子级,没有任何东西像上一个示例/演示那样强制伸展的高度100%确保Stack 的高度父级的高度相匹配,并且可以发生拆分。

Where the Stack is the only child of its parent, nothing forces it to stretch as in the last example/demo. A height of 100% ensures the Stack's height matches the parent's and the split can occur.

.stack:only-child {
  /* ↓ `height` in horizontal-tb writing mode */
  block-size: 100%;
}

用例

Use cases

Stack布局的潜在作用怎么估计都不过分。任何元素堆叠在一起的地方,很可能Stack应该生效。只有相邻的元素(例如网格单元格)不应受Stack的影响。然而,网格单元可能是Stacks 并且网格本身是Stack的成员。

The potential remit of the Stack layout can hardly be overestimated. Anywhere elements are stacked one atop another, it is likely a Stack should be in effect. Only adjacent elements (such as grid cells) should not be subject to a Stack. The grid cells are likely to be Stacks, however, and the grid itself a member of a Stack.

一个 3 x 2 的网格。 其中一个单元格被标记为 Stack,并且包含均匀间隔的子元素

A 3 by 2 grid. One of the cells is marked as a Stack, and contains evenly spaced child elements

发电机

The generator

代码生成器工具仅在随附的文档站点中可用。这是基本的解决方案,带有注释:

The code generator tool is only available in the accompanying documentation site. Here is the basic solution, with comments:

CSS

CSS

.stack {
  /* ↓ The flex context */
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
}

.stack > * {
  /* ↓ Any extant vertical margins are removed */
  margin-block: 0;
} 

.stack > * + * {
  /* ↓ Top margin is only applied to successive elements */
  margin-block-start: var(--space, 1.5rem;);
}

HTML

HTML

<div class="stack">
  <div><!-- child --></div>
  <div><!-- child --></div>
  <div><!-- etc --></div>
</div>

组件

The component

Stack 的自定义元素实现可供下载

A custom element implementation of the Stack is available for download.

道具接口

Props API

以下道具(属性)将导致Stack组件在更改时重新呈现。它们可以在浏览器开发人员工具中手动更改,或者作为继承的应用程序状态的主题。

The following props (attributes) will cause the Stack component to re-render when altered. They can be altered by hand—in browser developer tools—or as the subjects of inherited application state.

姓名 类型 默认 描述
空间 string "var(--s1)" 一个 CSSmargin
递归的 boolean false 空格是否递归应用(即不考虑嵌套级别)
分割后 number 在其后以自动边距拆分堆栈的元素

例子

Examples

基本的

Basic

<stack-l>
  <h2><!-- some text --></h2>
  <img src="path/to/some/image.svg" />
  <p><!-- more text --></p>
</stack-l>

嵌套

Nested

<stack-l space="3rem">
  <h2><!-- heading label --></h2>
  <stack-l space="1.5rem">
    <p><!-- body text --></p>
    <p><!-- body text --></p>
    <p><!-- body text --></p>
  </stack-l>
  <h2><!-- heading label --></h2>
  <stack-l space="1.5rem">
    <p><!-- body text --></p>
    <p><!-- body text --></p>
    <p><!-- body text --></p>
  </stack-l>
</stack-l>

递归

Recursive

<stack-l recursive>
  <div><!-- first level child --></div>
  <div><!-- first level sibling --></div>
  <div>
    <div><!-- second level child --></div>
    <div><!-- second level sibling --></div>
  </div>
</stack-l>

列表语义

List semantics

在某些情况下,浏览器应该将Stack解释为屏幕阅读器软件的列表。您可以使用以下 ARIA 属性来实现此目的。

In some cases, browsers should interpret the Stack as a list for screen reader software. You can use the following ARIA attribution to achieve this.

<stack-l role="list">
  <div role="listitem"><!-- item 1 content --></div>
  <div role="listitem"><!-- item 2 content --></div>
  <div role="listitem"><!-- item 3 content --></div>
</stack-l>

盒子

The Box

问题

The problem

正如我在Boxes中建立的那样,每个呈现的元素都会创建一个盒子形状。那么一个Box布局,封装成一个专用的Box组件有什么用呢?

As I established in Boxes, every rendered element creates a box shape. So what is the use of a Box layout, encapsulated as a dedicated Box component?

所有随后的布局都涉及将盒子排列在一起;以某种方式分布它们,使它们形成一个复合的视觉结构。例如,简单的Stack布局采用多个框并在它们之间插入垂直边距。

All the ensuing layouts deal in arranging boxes together; distributing them in some way such that they form a composite visual structure. For example, the simple Stack layout takes a number of boxes and inserts vertical margin between them.

重要的是Stack除了插入垂直边距外没有其他目的。如果它开始承担其他职责,它的工作描述将变得毫无意义,系统中的其他布局原语将不知道如何围绕Stack行事。

It is important the Stack is given no other purpose than to insert vertical margins. If it started to take on other responsibilities, its job description would become a nonsense, and the other layout primitives within the system wouldn't know how to behave around the Stack.

换句话说,这是一个关注点分离的问题。就像在计算机科学中一样,在视觉设计中,为每个工作部分赋予专门且独特的职责对您的系统有益。设计通过组合出现。

In other words, it's a question of separating concerns. Just as in computer science, in visual design it benefits your system to give each working part a dedicated and unique responsibility. The design emerges through composition.

一个盒子用于创建截然不同的基于盒子的组合

One box is used to create wildly different box-based compositions

Box在这个布局系统中的作用是处理任何可以被认为是单个元素固有的样式;不是从单个元素可能服从的元布局中规定、继承或推断的样式。但是这些是什么风格呢?感觉他们可以数不胜数。

The Box's role within this layout system is to take care of any styles that can be considered intrinsic to individual elements; styles which are not dictated, inherited, or inferred from the meta-layouts to which an individual element may be subjected. But which styles are these? It feels like they could be innumerable.

不必要。虽然某些 CSS 方法使您能够(或痛苦,取决于您的观点)将任何样式应用于单个元素,但有很多样式不需要以这种零碎的方式编写。font-familycolor和等样式line-height都可以继承或以其他方式全局应用,如全局和局部样式中所述。他们应该这样做,因为根据具体情况设置这些样式是多余的。

Not necessarily. While some approaches to CSS give you the power (or the pain, depending on your perspective) to apply any and every style to an individual element, there are plenty of styles that do not need to be written in this piecemeal way. Styles like font-family, color, and line-height can all be inherited or otherwise applied globally, as set out in Global and local styling. And they should, because setting these styles on a case-by-case basis is redundant.

:root {
  font-family: sans-serif;
}

.box {
  /* ↓ Not needed because the style is inherited */
  /* font-family: sans-serif; */
}

当然,您可能会在设计中使用多个font-family。但是应用默认(或“基本”)样式并在以后创建例外比将所有样式都设置为特殊情况更有效。

Of course, you are likely to employ more than one font-family in your design. But it is more efficient to apply default (or 'base') styles and later make exceptions than to style everything like it is a special case.

方便的是,全局样式往往是与品牌相关的样式——影响美学但不影响主题元素比例的样式。这个项目的目的是专门探索布局系统的创建,我们对品牌(或美学)本身不感兴趣。我们正在构建动态的、响应迅速的线框。美学可以应用在上面。

Conveniently, global styles tend to be branding related styles — styles that affect the aesthetics but not the proportions of the subject element(s). The purpose of this project is to explore the creation of a layout system specifically, and we are not interested in branding (or aesthetics) as such. We are building dynamic, responsive wireframes. Aesthetics can be applied on top.

两个尺寸相同但字体和背景不同的盒子

Two boxes with the same dimensions but different fonts and backgrounds

图片说明:相同的布局;审美不同

这限制了我们必须选择的属性数量。为了进一步减少这组潜在属性,我们必须问自己哪些特定于布局的属性最好由简单Box的父元素或祖先元素处理。

This limits the number of properties we have to choose from. To reduce this set of potential properties further, we have to ask ourselves which layout-specific properties are better handled by parent or ancestor elements of the simple Box.

解决方案

The solution

边距适用于Box,但仅由上下文引起——正如我已经确定的那样。宽度和高度也应该通过外部值(例如由 、 和 共同计算的宽度flex-basisflex-growflex-shrinkBox 中内容的性质推断。

Margin is applicable to the Box, but only as induced by context — as I've already established. Width and height should also be inferred, either by an extrinsic value (such as the width calculated by flex-basis, flex-grow, and flex-shrink working together) or by the nature of the content held inside the Box.

可以这样想:如果你没有任何东西可以放在盒子里,你就不需要盒子。如果你确实有东西要放在盒子里,最好的盒子是空间刚好够用的盒子。

Think of it like this: If you don't have anything to put in a box, you don't need a box. If you do have something to put in a box, the best box is one with just enough room and no more.

填充

Padding

填充是不同的。填充进入一个元素;它是内省的。填充应该是一个Box样式选项。padding问题是,我们的Box需要多少控制?毕竟,CSS 为我们提供了padding-top, padding-right, padding-bottom, 和padding-left, 以及padding速记。

Padding is different. Padding reaches into an element; it is introspective. Padding should be a Box styling option. The question is, how much control over padding for our Box is necessary? After all, CSS affords us padding-top, padding-right, padding-bottom, and padding-left, as well as the padding shorthand.

请记住,我们正在构建布局系统,而不是用于创建布局系统的 API。CSS 本身已经是 API。Box元素的所有边都应该有填充,或者根本没有边。为什么?因为具有特定(和不对称)填充的元素不是Box;这是试图解决更具体问题的另一件事。通常情况下,这个问题与添加元素之间的间距有关,这就是margin目的。边距延伸到元素的边界之外。

Remember we are building a layout system, and not an API for creating a layout system. CSS itself is already the API. The Box element should have padding on all sides, or no sides at all. Why? Because an element with specific (and asymmetrical) padding is not a Box; it's something else trying to solve a more specific problem. More often than not, this problem relates to adding spacing between elements, which is what margin is for. Margin extends outside the element's border.

左边的示例显示了使用填充来分隔元素如何导致边界接触的问题。 右边的例子显示了分隔边框的边距的正确使用

The left example shows how using padding to space elements creates an issue with borders coming into contact. The right example shows a correct use of margin that separates borders

在下面的示例中,我使用的padding值对应于我的modular scale上的第一个点。它应用于所有面,并且具有将Box的内容从其边缘移开的唯一目的。

In the below example, I'm using a padding value corresponding to the first point on my modular scale. It is applied to all sides, and has the singular purpose of moving the Box's content away from its edges.

.box {
  padding: var(--s1);
}

可见框

The visible box

如果Box具有类似盒子的形状,则它才是真正的Box 。是的,所有元素都是 box-shaped,但是Box通常应该您展示这一点。最常见的方法使用borderbackground.

A Box is only really a Box if it has a box-like shape. Yes, all elements are box-shaped, but a Box should typically show you this. The most common methods use either border or a background.

padding,border应该应用于所有方面或根本没有。在边框用于分隔元素的情况下,它们应该通过父级根据上下文应用,就像marginStack中一样。否则,边界将接触并“加倍”。

Like padding, border should be applied on all sides or none at all. In cases where borders are used to separate elements, they should be applied contextually, via a parent, like margin is in the Stack. Otherwise, borders will come into contact and 'double up'.

左侧:一个带边框的简单框。 右侧:相同的边框框,但由子元素划分,每个子元素由边框分隔

On the left: a simple box with a border. On the right: the same bordered box, but divided up by child elements, each separated by a border

图片说明:通过选择器应用一个border-top值,只显示子元素之间* + *的边框。没有人接触到父Box 的边框。

如果您以前编写过 CSS,那么您无疑已经习惯于background-color创建可视化的框形。更改background-color通常需要您更改color以确保内容仍然清晰可读。通过应用于该Boxcolor: inherit内的任何元素,这可以变得更容易。

If you've written CSS before, you've no doubt used background-color to create a visual box shape. Changing the background-color often requires you to change the color to ensure the content is still legible. This can be made easier by applying color: inherit to any elements inside that Box.

.box {
  padding: var(--s1);
}

.box * {
  color: inherit;
}

通过强制继承,您可以在一个地方更改color— 连同background-color—:在Box本身上。在下面的示例中,我使用一个.invert类来交换colorbackground-color属性。自定义属性可以在一个地方调整特定的明暗值。

By forcing inheritance, you can change the color—along with the background-color—in one place: on the Box itself. In the following example, I am using an .invert class to swap the color and background-color properties. Custom properties make it possible to adjust the specific light and dark values in one place.

.box {
  --color-light: #eee;
  --color-dark: #222;
  color: var(--color-dark);
  background-color: var(--color-light);
  padding: var(--s1);
}

.box * {
  color: inherit;
}

.box.invert {
  /* ↓ Dark becomes light, and light becomes dark */
  color: var(--color-light);
  background-color: var(--color-dark);
}

在没有边框的情况下,abackground-color不足以描述盒子形状。这是因为高对比度主题往往会消除背景。然而,通过使用透明outline的盒子形状可以恢复。

In the absence of a border, a background-color is insufficient for describing a box shape. This is because high contrast themes tend to eliminate backgrounds. However, by employing a transparent outline the box shape can be restored.

.box {
  --color-light: #eee;
  --color-dark: #222;
  color: var(--color-dark);
  background-color: var(--color-light);
  padding: var(--s1);
  outline: 0.125rem solid transparent;
  outline-offset: -0.125rem;
}

这是如何运作的?当高对比度主题未运行时,轮廓是不可见的。该outline属性对布局也没有影响(如果存在,它会从元素中长出以覆盖其他元素)。当打开 Windows 高对比度模式时,它会为轮廓赋予颜色并绘制方框。

How does this work? When a high contrast theme is not running, the outline is invisible. The outline property also has no impact on layout (it grows out from the element to cover other elements if present). When Windows High Contrast Mode is switched on, it gives the outline a color and the box is drawn.

负值outline-offset将轮廓移动Box周边内,因此它的行为类似于边框,不再增加框的整体尺寸。

The negative outline-offset moves the outline inside the Box's perimeter so it behaves like a border and no longer increases the box's overall size.

用例

Use cases

Box的基本且多产的用例是对某些内容进行分组和划分。此内容可能显示为消息或“注释”等文本流内容,显示为网格中的一张卡片,或显示为已定位对话框元素的内部包装。

The basic, and highly prolific, use case for a Box is to group and demarcate some content. This content may appear as a message or 'note' among other, textual flow content, as one card in a grid of many, or as the inner wrapper of a positioned dialog element.

盒子的不同使用方式。 在文本中,作为网格中的单元格,并相互嵌套

The different ways boxes can be used. In among text, as cells in a grid, and nested inside each other

您也可以只组合盒子来制作一些有用的作品。带有 'header' 元素的Box可以由两个嵌套在另一个父Box中的兄弟框组成。

You can also combine just boxes to make some useful compositions. A Box with a 'header' element can be made from two sibling boxes, nested inside another, parent Box.

一个没有边框但没有填充的盒子包含两个兄弟盒子,每个盒子都有填充。 顶部框有一个倒置的配色方案,以创建一个黑色的标题区域。

A box with no a border but no padding houses two sibling boxes, each with padding. The top box has an inverted color scheme to create a black header area.

发电机

The generator

使用此工具生成基本的Box CSS 和 HTML。它提供了创建基本的双色调框的能力,包括亮暗和暗亮(“反转”)主题。有关更多信息,请参阅可见框部分。

Use this tool to generate basic Box CSS and HTML. It provides the ability to create basic, two-tone boxes, including light-on-dark and dark-on-light ('inverted') themes. See the The visible box section for more.

代码生成器工具仅在随附的文档站点中可用。这是基本的解决方案,带有注释:

The code generator tool is only available in the accompanying documentation site. Here is the basic solution, with comments:

CSS

CSS

.box {
  /* ↓ Padding set to the first point on the modular scale */
  padding: var(--s1);
  /* ↓ Assumes you have a --border-thin var */
  border: var(--border-thin) solid;
  /* ↓ Always apply the transparent outline, for high contrast mode */
  outline: var(--border-thin) transparent;
  outline-offset: calc(var(--border-thin) * -1);
  /* ↓ The light and dark color vars */
  --color-light: #fff;
  --color-dark: #000;
  color: var(--color-dark);
  background-color: var(--color-light);
}

.box * {
  /* ↓ Force colors to inherit from the parent
  and honor inversion (below) */
  color: inherit;
}

.box.invert {
  /* ↓ The color vars inverted */
  color: var(--color-light);
  background-color: var(--color-dark);
}

HTML

HTML

<div class="box">
  <-- the box's contents -->
</div>

组件

The component

Box 的自定义元素实现可供下载

A custom element implementation of the Box is available for download.

道具接口

Props API

以下道具(属性)将导致Box组件在更改时重新呈现。它们可以在浏览器开发人员工具中手动更改,或者作为继承的应用程序状态的主题。

The following props (attributes) will cause the Box component to re-render when altered. They can be altered by hand—in browser developer tools—or as the subjects of inherited application state.

姓名 类型 默认 描述
填充 string "var(--s1)" 一个 CSSpadding
边框宽度 string "var(--border-thin)" 一个 CSSborder-width
倒置 boolean false 是否应用反转主题。仅推荐用于灰度设计。

例子

Examples

基本框

Basic box

Box带有默认的内边距和边框。该padding值设置为模比例( var(--s1)) 上的第一个点,并border-width使用该var(--border-thin)变量。

The Box comes with default padding and border. The padding value is set to the first point on the modular scale (var(--s1)) and the border-width uses the var(--border-thin) variable.

<box-l>
  <!-- contents of the box -->
</box-l>

堆叠中的盒子

A Box within a Stack

Stack的上下文中,如果 Box 前面有同级元素,则Box将被应用。margin-top

In the context of a Stack, the Box will have margin-top applied if it is preceded by a sibling element.

<stack-l>
  <p>...</p>
  <blockquote>...</blockquote>
  <box-l>
    <!-- Box separated by vertical margins -->
  </box-l>
  <p>...</p>
  <div role="figure">...</div>
</stack-l>

带标题的框

Box with a header

来自Use cases的嵌套Box示例的实现。布尔属性使用 反转颜色。invertfilter: invert(100%)

An implementation of the nested Box example from Use cases. The invert boolean attribute inverts the colors using filter: invert(100%).

<box-l padding="0">
  <box-l borderWidth="0" invert>head</box-l>
  <box-l borderWidth="0">body</box-l>
</box-l>

中心

The Center

问题

The problem

在 HTML 的早期,有许多表现元素;元素的设计纯粹是为了影响其内容的外观。这<center>是一个这样的元素,但长期以来一直被认为是过时的。奇怪的是,一些浏览器仍然支持它包括谷歌的 Chrome。这大概是因为谷歌的搜索主页仍然使用 a<center>来居中对齐其著名的搜索输入。

In the early days of HTML, there were a number of presentational elements; elements devised purely to affect the appearance of their content. The <center> was one such element, but has long since been considered obsolete. Curiously, it is still supported in some browsers, including Google's Chrome. Presumably this is because Google's search homepage still uses a <center> to center-justify its famous search input.

撇开科技巨头异想天开地使用废弃元素不谈,我们在 2000 年代基本上不再使用展示性标记。通过将样式设置为一项单独的技术(CSS)的责任,我们能够分别管理样式和结构。因此,艺术方向的改变将不再意味着重构我们的内容。

Tech' giants' whimsical usage of defunct elements aside, we mostly moved away from using presentational markup in the 2000s. By making styling the responsibility of a separate technology—CSS—we were able to manage style and structure separately. Consequently, a change in art direction would no longer mean reconstituting our content.

后来我们发现,纯粹根据语义和上下文来设计 HTML 样式是相当有野心的,并导致了一些笨拙的选择器,比如

We later discovered that styling HTML purely in terms of semantics and context was rather ambitious, and led to some unwieldy selectors like

body > div > div > a {
  /* 
  Link styles specifically for links 
  nested two <div>s inside the body element 
  */
}

为了更容易维护 CSS 和样式模块化,我们中的许多人采用了使用类的折衷方案。因为类可以放在任何元素上,所以我们可以自由地设置样式,例如,以完全相同的方式<div>识别非语义或屏幕阅读器,使用相同的标记,但不会影响可访问性。<nav>

For the sake of easier CSS maintenance and style modularity many of us adopted a compromise position using classes. Because classes can be placed on any element, we are free to style, say, a non-semantic <div> or a screen reader recognized <nav> in exactly the same way, using the same token, but without compromising on accessibility.

<div class="text-align:center"></div>

<nav class="text-align:center"></nav>

<center>所做的一切都是text-align: center居中对齐的文本。对于大多数内容——尤其是包含段落文本的内容——你会想要避免它。可读性很差

All <center> did, and all text-align: center does, is center-justify text. And for most content—especially content that includes paragraph text—you'll want to avoid it. It's terrible for readability.

但是有用的是可以创建水平居中列的组件使用这样的组件,我们可以在任何容器内创建一个居中的“条带”内容,限制其宽度以保持合理的尺寸

But what would be useful is a component that can create a horizontally centered column. With such a component, we could create a centered 'stripe' of content within any container, capping its width to preserve a reasonable measure.

解决方案

The solution

解决居中列的最简单方法之一是使用auto边距。关键字,auto顾名思义,指示浏览器为您计算保证金。它可能是算法CSS 技术最基本的示例之一:它遵循浏览器的逻辑来确定布局,而不是“硬编码”特定值。

One of the easiest ways to solve for a centered column is to use auto margins. The auto keyword, as its name suggests, instructs the browser to calculate the margin for you. It's perhaps one of the most rudimentary examples of an algorithmic CSS technique: one that defers to the browser's logic to determine the layout rather than 'hard coding' a specific value.

具有自动左边距的元素被推到右边。 具有自动右边距的元素被推到左边。 具有自动左右边距的元素被推到中心

An element with an auto left margin is pushed to the right. An element with an auto right margin is pushed to the left. An element with both an auto left and right margin is pushed to the center

我的第一个居中列将使用margin速记,通常在<body>元素上。

My first centered columns would use the margin shorthand, often on the <body> element.

.center {
  max-width: 60ch;
  margin: 0 auto;
}

速记属性的问题——尽管它节省了几个字节——是你必须声明某些值,即使它们不适用。仅设置实现您尝试的特定布局所需的 CSS 值非常重要。您永远不知道您可能正在撤销哪些推断或继承的值。

The trouble with the shorthand property—though it saves a few bytes—is that you have to declare certain values, even when they are not applicable. It's important to only set the CSS values needed to achieve the specific layout you are attempting. You never know what inferred or inherited values you might be undoing.

例如,我可能想将我的<center-l>自定义元素放在Stack上下文中。Stack sets margin-topon its children,任何<center-l>withmargin: 0 auto都会撤消它。

For example, I might want to place my <center-l> custom element within a Stack context. Stack sets margin-top on its children, and any <center-l> with margin: 0 auto would undo that.

Stack 上下文中的 Center 元素的上边距为零,并向上推到其上方的元素

The Center element in a Stack context has a zero top margin and is pushed up against the element above it

相反,我可以使用显式margin-leftmargin-right属性。然后,将保留上下文应用的任何垂直边距,并且该<center-l>组件将准备好在其他布局组件中进行组合/嵌套。

Instead, I could use the explicit margin-left and margin-right properties. Then, any vertical margins contextually applied would be preserved, and the <center-l> component would be primed for composition/nesting among other layout components.

.center {
  max-width: 60ch;
  margin-left: auto;
  margin-right: auto;
}

更好的是,我可以使用单个margin-inline 逻辑属性。如Boxes中所述,逻辑属性与方向和维度映射有关,因此与更广泛的语言兼容。我们也在使用max-inline-size代替max-width.

Even better, I could use a single margin-inline logical property. As described in Boxes, logical properties pertain to direction and dimension mappings and are—as such—compatible with a wider range of languages. We are also using max-inline-size in place of max-width.

.center {
  max-inline-size: 60ch;
  margin-inline: auto;
}

最低限度margin

Minimum margin

在比 窄的上下文中60ch,内容当前将与父元素或视口的任一侧齐平。我们不应让这种情况发生,而应确保两边的空间最小。

In a context narrower than 60ch, the contents will currently be flush with either side of the parent element or viewport. Rather than letting this happen, we should ensure a minimum space on either side.

我需要以保持居中和60ch最大宽度的方式来解决这个问题。由于我们无法auto进行计算(如calc(auto + 1rem)),我们可能应该推迟填充。

I need to go about this in such a way that preserves centering, and the 60ch maximum width. Since we can't enter auto into a calculation (like calc(auto + 1rem)), we should probably defer to padding.

左图是一个宽视口,其中有足够的空间用于边距和填充。 在右侧,视口较小,这意味着只有填充空间

The left image is of a wide viewport where there is enough room for the margin and the padding. On the right, the viewport is smaller, meaning there is only room for padding

但我必须警惕盒子模型。如果按照Boxes中的建议,我已将所有元素设置为 adopt box-sizing: border-box,则添加到我的任何填充<center-l>都将有助于60ch总数。也就是说,添加padding会让我的元素的内容变窄。但是,正如Axioms中所述, CSS 是为异常而设计的。我只需要覆盖border-boxwith content-box,并允许填充从60ch内容阈值“增长”。

But I have to be wary of the box model. If, as suggested in Boxes, I have set all elements to adopt box-sizing: border-box, any padding added to my <center-l> will contribute to the 60ch total. In other words, adding padding will make the content of my element narrower. However, as covered in Axioms CSS is designed for exceptions. I just need to override border-box with content-box, and allow the padding to 'grow out' from the 60ch content threshold.

左侧版本使用 border-box,所以最大宽度包括填充,缩小内容。 正确的版本通过使用 content-box 纠正了这个问题

The left version uses border-box, so the max width includes the padding, shrinking the content. The right version corrects this by using content-box

这是一个保留 的版本,但确保两侧60ch max-width至少有“边距”(是基于自定义属性的模块化比例尺的第一个点)。var(--s1)--s1

Here's a version that preserves the 60ch max-width, but ensures there are, at least, var(--s1) “margins” on either side (--s1 is the first point on the custom property-based modular scale).

.center {
  box-sizing: content-box;
  max-inline-size: 60ch;
  margin-inline: auto;
  padding-inline-start: var(--s1);
  padding-inline-end: var(--s1);
}

内在定心

Intrinsic centering

auto保证金解决方案由来已久且可完美使用。但是有机会使用 Flexbox 布局模块来支持内部居中。也就是说,根据元素自然的、基于内容的宽度将元素居中。考虑以下代码。

The auto margin solution is time-honoured and perfectly serviceable. But there is an opportunity using the Flexbox layout module to support intrinsic centering. That is, centering elements based on their natural, content-based widths. Consider the following code.

.center {
  box-sizing: content-box;
  max-inline-size: 60ch;
  margin-inline: auto;
  display: flex;
  flex-direction: column;
  align-items: center;
}

<center-l>组件内部,我希望内容垂直排列,作为一列,因此flex-direction: column. 这允许我设置align-items: center,这将使任何孩子居中,而不管他们的宽度如何。

Inside a <center-l> component, I would expect the contents to be arranged vertically, as a column, hence flex-direction: column. This allows me to set align-items: center, which will center any children regardless of their width.

结果是任何比宽度窄的元素60ch都会自动在60ch-wide 区域内居中。这些元素可以包括自然的小元素,如按钮,或在 下有自己的max-width集合的元素60ch

The upshot is any elements that are narrower than 60ch will be automatically centered within the 60ch-wide area. These elements can include naturally small elements like buttons, or elements with their own max-width set under 60ch.

在左侧,没有固有的居中,这意味着按钮向左对齐。 右边有固有的居中,所以按钮与中心对齐

On the left, there is no intrinsic centering, meaning the button is aligned to the left. On the right there is intrinsic centering, so the button is aligned to the center

图片说明:插图段落受制于align-items: center,但自然会占用所有可用空间(它们是没有设置宽度 的块元素)。

用例

Use cases

每当您希望某些东西水平居中时,中心就是您的朋友。在下面的示例中,我模拟了Every Layout文档站点的基本布局(您现在可能正在查看,除非您正在阅读 EPUB)。它包含一个Sidebar ,右侧有一个Center 。使用Stacks在侧边栏和中心垂直分隔元素。应用了布尔值的嵌套中心将“启动演示”按钮居中。intrinsic

Whenever you wish something to be horizontally centered, the Center is your friend. In the following example, I am emulating the basic layout for the Every Layout documentation site (which you may be looking at now, unless you’re reading the EPUB). It comprises a Sidebar, with a Center to the right-hand side. Elements are vertically separated in both the sidebar and the Center using Stacks. A nested Center with the intrinsic boolean applied centers the 'Launch demo' button.

(您可能需要在其自己的(桌面)窗口中启动它以查看居中操作。)

(You may need to launch it in its own (desktop) window to see the centering in action.)

此交互式演示仅在Every Layout站点上可用。

This interactive demo is only available on the Every Layout site.

发电机

The Generator

使用此工具生成基本的Center CSS 和 HTML。

Use this tool to generate basic Center CSS and HTML.

代码生成器工具仅在随附的文档站点中可用。这是基本的解决方案,带有注释(省略了固有的居中代码):

The code generator tool is only available in the accompanying documentation site. Here is the basic solution, with comments (omitting the intrinsic centering code):

CSS

CSS

.center {
  /* ↓ Remove padding from the width calculation */
  box-sizing: content-box;
  /* ↓ The maximum width is the maximum measure */
  max-inline-size: 60ch;
  /* ↓ Only affect horizontal margins */
  margin-inline: auto;
  /* ↓ Apply the minimum horizontal space */
  padding-inline-start: var(--s1);
  padding-inline-end: var(--s1);
}

HTML

HTML

<div class="center">
  <!-- centered content -->
</div>

组件

The Component

该中心的自定义元素实现可供下载

A custom element implementation of the Center is available for download.

道具接口

Props API

以下道具(属性)将导致中心组件在更改时重新呈现。它们可以在浏览器开发人员工具中手动更改,或者作为继承的应用程序状态的主题。

The following props (attributes) will cause the Center component to re-render when altered. They can be altered by hand—in browser developer tools—or as the subjects of inherited application state.

姓名 类型 默认 描述
最大限度 string "var(--measure)" 一个 CSSmax-width
和文本 boolean false 文本也居中对齐 ( text-align: center)
排水沟 boolean 0 内容两边的最小空间
固有的 boolean false 根据内容宽度居中子元素

例子

Examples

基本的

Basic

您可以通过将Stack嵌套在Center内的Box内来创建单栏网页。默认情况下填充的Box意味着不需要使用prop 向中心的任一侧提供填充。gutters

You can create a single column web page just by nesting a Stack inside a Center inside a Box. The Box being padded by default means providing padding to either side of the Center using the gutters prop is not necessary.

<box-l>
  <center-l>
    <stack-l>
      <!-- Any flow content here (headings, paragraphs, etc) -->
    </stack-l>
  <center-l>
</box-l>

文档布局

Documentation layout

来自用例中示例的标记。在示例中,添加了 WAI-ARIA 地标角色以支持屏幕阅读器。请注意,中心已包装在通用<div>容器中。这<div>受限于Sidebar 的布局逻辑,使Center可以在其中应用自己的逻辑。当它开始占用的可用水平空间减少时,侧边栏会换行。阅读边栏以获得完整的解释。<div>66.666%

The markup from the example in Use cases. In the example, WAI-ARIA landmark roles have been added for screen reader support. Note that the Center has been wrapped in a generic <div> container. This <div> is subject to the Sidebar's layout logic, freeing the Center to apply its own logic inside it. The Sidebar wraps when this <div> starts to take up less that 66.666% of the available horizontal space. Read Sidebar for a full explanation.

<sidebar-l contentMin="66.666%" sideWidth="10rem">
  <stack-l role="navigation">
    <!-- navigation items (API refs) -->
  </stack-l>
  <div>
    <center-l role="main">
      <!-- main content for the page -->
    </center-l>
  </div>
</sidebar-l>

垂直和水平居中

Vertical and horizontal centering

使用组合和Cover组件,水平垂直居中元素是微不足道的。此处使用intrinsic布尔值来使段落居中,而不管其内容的宽度如何。

Using composition and the Cover component, it's trivial to horizontally and vertically center an element. The intrinsic boolean is used here to center the paragraph regardless of its content's width.

<cover-l centered="center-l">
  <center-l intrinsic>
    <p>I am in the absolute center.</p>
  </center-l>
</cover-l>

集群

The Cluster

问题

The problem

有时网格是布局内容的合适框架,因为您希望内容严格对齐作为行和列边界的水平线和垂直线。

Sometimes grids are an appropriate framework for laying out content, because you want that content to align strictly to the horizontal and vertical lines that are those row and column boundaries.

但并不是所有事情都受益于这种规定的严格性——至少不是在所有情况下。文本本身不能遵守网格的限制,因为单词有不同的形状和长度。相反,浏览器的文本换行算法会分配文本以尽可能地填充可用空间。左对齐的文本有一个“参差不齐”的右边缘,因为每一行都不可避免地具有不同的长度。

But not everything benefits from this prescribed rigidity — at least not in all circumstances. Text itself cannot adhere to the strictures of a grid, because words come in different shapes and lengths. Instead, the browser's text wrapping algorithm distributes the text to fill the available space as best it can. Left-aligned text has a 'ragged' right edge, because each line will inevitably be of a different length.

多亏了前导 ( line-height) 和单词空间(U+0020字符,或者SPACE你的按键),单词可以合理均匀地间隔,尽管它们的形式多种多样。当我们处理不确定大小/形状的元素组时,我们应该经常希望它们以类似的流动方式分布。

Thanks to leading (line-height) and word spaces (the U+0020 character, or a SPACE keypress to you), words can be reasonably evenly spaced, despite their diversity of form. Where we am dealing with groups of elements of an indeterminate size/shape, we should often like them to distribute in a similarly fluid way.

一种方法是将这些元素的display值设置为inline-block。这使您可以控制paddingmargin同时保留固有大小。也就是说,inline-block元素的大小仍然根据其内容的维度来确定。

One approach is to set these elements' display value to inline-block. This gives you some control over padding and margin while retaining intrinsic sizing. That is, inline-block elements are still sized according to the dimensions of their content.

但是,与单词一样,inline-block元素仍然由空格字符分隔(在源代码中存在)。此空间的宽度将添加到margin您申请的任何内容。可以删除此空格,但只能通过font-size: 0在父项上设置并重置子项的值来删除。

However, like words, inline-block elements are still separated by space characters (where present in the source). The width of this space will be added to any margin you apply. This space can be removed, but only by setting font-size: 0 on the parent, and resetting the value on the children.

.parent {
  font-size: 0;
}

.parent > * {
  font-size: 1rem;
}

这样做的缺点是我们不能em在我的子元素上使用,因为它等于0。相反,我们需要用单位设置元素的font-size相对值。必须以这种方式重置字体大小有些限制。:rootrem

This has the disadvantage that we can't use em on my child elements because it would be equal to 0. Instead, we need to set the font-size relative to the :root element with the rem unit. Font size having to be reset in this fashion is somewhat restrictive.

即使消除了空间,仍然存在与包装相关的边距问题。如果将边距应用于连续元素,则在没有换行的情况下外观是可以接受的。但是在确实发生换行的地方,对齐的一侧出现了意想不到的缩进,并且垂直间距完全消失了。

Even with the space eliminated, there are still wrapping-related margin issues. If margin is applied to successive elements, the appearance is acceptable where no wrapping occurs. But where wrapping does occur, there are unexpected indents against the aligned side, and vertical spacing is missing entirely.

元素堆叠不均匀和缺失的空间

Elements stack up with uneven and missing spaces

通过在每个元素上放置右边距和底部边距,可以进行部分修复。

A partial fix is possible by placing right and bottom margins on each element.

现在(左)对齐侧没有缩进可见

No indents on the (left) aligned side are now visible

然而,这只解决了左对齐的情况——加上在多余的边距与父元素的填充交互时会出现双倍空间:

However, this only solves the left-aligned case — plus doubled-up space occurs where excess margin interacts with the padding of a parent element:

该框在其右边缘和底部边缘内显示更大的填充(实际上是填充和边距的组合)

The box shows larger padding (actually a combination of padding and margin) inside its right and bottom edges

解决方案

The solution

要创建高效且易于管理的设计系统,我们需要为布局问题设计稳健的通用解决方案。

To create an efficient and manageable design system, we need to devise robust, general solutions to our layout problems.

首先,我们让父级成为 Flexbox 上下文。这允许我们将元素配置成簇,而不必处理不需要的单词空间。与使用浮动相比,它还有几个优点:我们不需要为浮动提供明确的修复,并且垂直对齐(使用align-items)是可能的。

First, we make the parent a Flexbox context. This allows us to configure the elements into clusters, without having to deal with undesirable word spaces. It also has several advantages over using floats: we do not need to provide a clear fix for one, and vertical alignment (using align-items) is possible.

.cluster {
  display: flex;
  flex-wrap: wrap;
}

添加和隐藏边距

Adding and obscuring margin

我们目前可以添加尊重环绕行为的边距的唯一方法是对称地添加它们,而不管选择的对齐方式如何;四面八方。不幸的是,这会将元素与它们接触的任何边缘分开。

The only way we can currently add margins that respect wrapping behaviour, irrespective of the alignment chosen, is to add them symmetrically; to all sides. Unfortunately, this separates the elements from any edge with which they come into contact.

在这两个示例布局中,元素与左边缘和下边缘分开

The elements are separated from the left and bottom edges in these two example layouts

请注意,子元素与父元素边缘之间的空间值始终是两个子元素之间空间的一半(因为它们的边距合并在一起)。解决方案是在父级上使用负边距将子级拉到自己的边缘:

Note the value of the space between a child element and a parent element's edge is always half that of the space between two child elements (since their margins combine together). The solution is to use a negative margin on the parent to pull the children to its own edges:

箭头表示拉动动作。 子项的边距现在出现在父项的边缘之外。

Arrows show the pulling action. The margins of the children now appear outside the edges of the parent.

通过使用自定义属性,我们可以更轻松地在Cluster组件中创建空间。该--space变量定义了元素之间所需的间距,并calc()相应地调整该值。请注意,包含另一个包装器元素以将相邻内容与负边距隔离开来。我们仍然希望组件尊重父Stack组件应用的空白。

We can make authoring space in the Cluster component easier by using custom properties. The --space variable defines the desired spacing between elements, and calc() adapts this value accordingly. Note that a further wrapper element is included to insulate adjacent content from the negative margin. We still want the component to respect white space applied by a parent Stack component.

.cluster {
  --space: 1rem;
}

.cluster > * {
  display: flex;
  flex-wrap: wrap;
  /* ↓ multiply by -1 to negate the halved value */
  margin: calc(var(--space) / 2 * -1);
}

.cluster > * > * {
  /* ↓ half the value, because of the 'doubling up' */
  margin: calc(var(--space) / 2);
}

gap物业

The gap property

我想您会同意上述技术有点笨拙。在某些情况下,它还会导致出现水平滚动条。幸运的是,从 2021 年年中开始,所有主流浏览器现在都支持gapFlexbox 属性。该属性在子元素之间gap注入间距,从而消除了对负边距和附加包装元素的需要。甚至可以退休,因为价值就是这样!calc()gap

I think you’ll agree the above technique is a bit unwieldy. It can also cause the horizontal scrollbar to appear, under some circumstances. Fortunately, as of mid-2021, all major browsers now support the gap property with Flexbox. The gap property injects spacing between the child elements, doing away with the need for both negative margins and the additional wrapper element. Even the calc() can be retired, since the gap value is just that!

.cluster {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space, 1rem);
}

优雅降级

Graceful degradation

尽管对 的支持图片令人放心gap,但我们应该注意不支持它的浏览器中的布局。有问题的是,gap可能支持 Grid 布局模块(参见Grid)但不支持 Flexbox,因此gap@supports块中使用可能会产生误报。

Despite the reassuring support picture for gap, we should be mindful of the layout in browsers where it isn’t supported. Problematically, gap may be supported for the Grid layout module (see Grid) but not for Flexbox, so using gap in a @supports block can give a false positive.

gap仅支持 Grid 模块的浏览器中,以下内容将导致没有边距或被 gap应用。

In browsers where gap is only supported for the Grid module, the following would lead to no margin or gap being applied.

/* This won’t work */
.cluster > * {
  display: flex;
  flex-wrap: wrap;
  margin: 1rem;
}

@supports (gap: 1rem) {
  .cluster > * {
    margin: 0;
  }

  .cluster {
    gap: var(--space, 1rem);
  }  
}

从今天开始,我们建议gap在不进行功能检测的情况下使用,接受布局将在旧版浏览器中变得齐平。如果您愿意,我们会在上面包含负边距技术。

As of today, we recommend using gap without feature detection, accepting that layouts will become flush in older browsers. We include the negative margin technique above if that’s your preference instead.

理由

Justification

元素的组或justify-content可以取任何值,无论换行如何,空间/间隙都将得到尊重。将集群向右对齐是justify-content: flex-end.

Groups or clusters of elements can take any justify-content value, and the space/gap will be honored regardless of wrapping. Aligning the Cluster to the right would be a case for justify-content: flex-end.

在接下来的演示中,一个Cluster包含一个链接关键字列表。它被放置在一个padding值等于集群空间值的框内。

In the demo to follow, a Cluster contains a list of linked keywords. This is placed inside a box with a padding value equal to that of the Cluster’s space.

此交互式演示仅在Every Layout站点上可用。

This interactive demo is only available on the Every Layout site.

用例

Use cases

组件适用于任何长度不同且易于缠绕的元素组。一起出现在表单末尾的按钮是理想的候选者,标签列表、关键字或其他元信息也是如此。使用Cluster将任何水平布局的元素组向左或向右或居中对齐。

Cluster components suit any groups of elements that differ in length and are liable to wrap. Buttons that appear together at the end of forms are ideal candidates, as well as lists of tags, keywords, or other meta information. Use the Cluster to align any groups of horizontally laid out elements to the left or right, or in the center.

通过应用justify-content: space-betweenalign-items: center您甚至可以设置页面标题的徽标和导航。这将自然换行,不需要@media断点:

By applying justify-content: space-between and align-items: center you can even set out your page header’s logo and navigation. This will wrap naturally, and without the need for an @media breakpoint:

徽标与较大视口中的一行导航链接水平分开。 链接在狭窄的视口中环绕在徽标下方。 元素之间的边距保持均匀

The logo is separated horizontally from a line of navigation links in the larger viewport. The links wrap under the logo in a narrow viewport. The margins between elements remain even

图片说明:导航列表将在徽标下方换行,此时没有空间容纳未换行的内容(最大宽度)。这意味着我们避免了导航链接同时出现在徽标旁边下方的情况。

下面是上述标题布局的演示,使用嵌套的Cluster结构。外部Cluster使用justify-content: space-betweenand align-items: center。导航链接的Cluster用于justify-content: flex-start在包装后将其项目左对齐。

Below is a demo of the aforementioned header layout, using a nested Cluster structure. The outer Cluster uses justify-content: space-between and align-items: center. The Cluster for the navigation links uses justify-content: flex-start to align its items to the left after wrapping.

此交互式演示仅在Every Layout站点上可用。

This interactive demo is only available on the Every Layout site.

发电机

The generator

使用此工具生成基本的Cluster CSS 和 HTML。

Use this tool to generate basic Cluster CSS and HTML.

代码生成器工具仅在随附的文档站点中可用。这是基本的解决方案,带有注释:

The code generator tool is only available in the accompanying documentation site. Here is the basic solution, with comments:

CSS

CSS

.cluster {
  /* ↓ Set the Flexbox context */
  display: flex;
  /* ↓ Enable wrapping */
  flex-wrap: wrap;
  /* ↓ Set the space/gap */
  gap: var(--space, 1rem);
  /* ↓ Choose your justification (flex-start is default) */
  justify-content: center;
  /* ↓ Choose your alignment (flex-start is default) */
  align-items: center;
}

HTML

HTML

<ul class="cluster">
  <li><!-- child --></li>
  <li><!-- child --></li>
  <li><!-- etc --></li>
</ul>

组件

The component

集群的自定义元素实现可供下载

A custom element implementation of the Cluster is available for download.

道具接口

Props API

以下道具(属性)将导致Cluster组件在更改时重新呈现。它们可以在浏览器开发人员工具中手动更改,或者作为继承的应用程序状态的主题。

The following props (attributes) will cause the Cluster component to re-render when altered. They can be altered by hand—in browser developer tools—or as the subjects of inherited application state.

姓名 类型 默认 描述
证明合法 string "flex-start" 一个 CSSjustify-content
对齐 string "flex-start" 一个 CSSalign-items
空间 string "var(--s1)" 一个 CSSgap值。聚集的子元素之间的最小空间。

例子

Examples

基本的

Basic

使用默认值。

Using the defaults.

<cluster-l>
  <!-- child element here -->
  <!-- another child element -->
  <!-- etc -->
  <!-- etc -->
  <!-- etc -->
  <!-- etc -->
</cluster-l>

列表

List

由于Clusters通常代表类似元素的组,因此它们受益于被标记为列表。列表元素以非视觉方式向屏幕阅读器软件呈现信息。重要的是屏幕阅读器用户知道存在一个列表,以及它包含多少项目。

Since Clusters typically represent groups of similar elements, they benefit from being marked up as a list. List elements present information non-visually, to screen reader software. It’s important screen reader users are aware there is a list present, and how many items it contains.

由于我们的自定义元素<cluster-l>不是 a <ul><li>元素不能没有<ul>父元素存在),我们可以使用 ARIA 来提供列表语义:role="list"role="listitem"

Since our custom element <cluster-l> is not a <ul> (and <li> elements cannot exist without a <ul> parent) we can provide the list semantics using ARIA instead: role="list" and role="listitem":

<cluster-l role="list">
  <div role="listitem"><!-- content of first list item --></div>
  <div role="listitem"><!-- content of second list item --></div>
  <div role="listitem"><!-- etc --></div>
  <div role="listitem"><!-- etc --></div>
</cluster-l>

边栏

The Sidebar

问题

The problem

当你的视觉设计的媒介尺寸和设置不确定时,即使是像把东西放在其他东西旁边这样简单的东西也是一个难题。水平空间是否足够?而且,即使有,布局是否会充分利用垂直空间?

When the dimensions and settings of the medium for your visual design are indeterminate, even something simple like putting things next to other things is a quandary. Will there be enough horizontal space? And, even if there is, will the layout make the most of the vertical space?

左边的例子显示了相邻元素过多的内容溢出。 右图为相邻元素高度不同时产生的难看缝隙

The left example shows the content overflowing where there are too many adjacent elements. The right example shows the unsightly gaps produced when there are adjacent elements of different heights

如果两个相邻项目没有足够的空间,我们倾向于使用断点(基于宽度的@media查询)来重新配置布局,并将两个项目一个放在另一个之上。

Where there’s not enough space for two adjacent items, we tend to employ a breakpoint (a width-based @media query) to reconfigure the layout, and place the two items one atop the other.

重要的是我们使用内容而不是基于设备@media查询。也就是说,我们应该在内容需要重新配置的任何地方进行干预,而不是坚持像720px和这样的任意宽度1024px。设备的大量增加意味着没有一套真正的标准尺寸可供设计。

It’s important we use content rather than device based @media queries. That is, we should intervene anywhere the content needs reconfiguration, rather than adhering to arbitrary widths like 720px and 1024px. The massive proliferation of devices means there’s no real set of standard dimensions to design for.

但即使是这种策略也有一个根本性的缺点:@media对宽度的查询与视口宽度有关,与实际可用空间无关。组件可能出现在300px宽容器中,也可能出现在更宽敞的宽容500px器中。但是视口的宽度在这两种情况下都是相同的,所以没有什么可以“响应”的。

But even this strategy has a fundamental shortcoming: @media queries for width pertain to the viewport width, and have no bearing on the actual available space. A component might appear within a 300px wide container, or it might appear within a more generous 500px wide container. But the width of the viewport is the same in either case, so there’s nothing to “respond” to.

显示两个相同宽度的视口。 在一个中,组件占据了整个宽度,在下一个中,它被一个狭窄的容器所限制

Shows two viewports of the same width. In one, the component takes up the whole width, in the next it is constrained by a narrow container

设计系统倾向于对可能出现在不同上下文和空间之间的组件进行分类,因此这是一个真正的问题。只有使用像有争议的容器查询这样的功能,我们才能教会我们的组件布局完全了解上下文

Design systems tend to catalogue components that can appear between different contexts and spaces, so this is a real problem. Only with a capability like the mooted container queries might we teach our component layouts to be fully context aware.

在某些方面,CSS Flexbox 模块及其提供的flex-basis,已经可以根据上下文很好地管理自己的布局。考虑以下代码:

In some respects, the CSS Flexbox module, with its provision of flex-basis, can already govern its own layout, per context, rather well. Consider the following code:

.parent {
  display: flex;
  flex-wrap: wrap;
}

.parent > * {
  flex-grow: 1;
  flex-shrink: 1;
  flex-basis: 30ch;
}

flex-basis值实质上确定了主题子元素的理想目标宽度。启用增长、收缩和环绕后,可用空间将被用完,这样每个元素都尽可能接近宽度30ch。在一个> 90ch宽容器中,每行可能出现三个以上的孩子。Between60ch90ch只能出现两个项目,其中一个项目占据最后一行的全部(如果总数为奇数)。

The flex-basis value essentially determines an ideal target width for the subject child elements. With growing, shrinking, and wrapping enabled, the available space is used up such that each element is as close to 30ch wide as possible. In a > 90ch wide container, more than three children may appear per row. Between 60ch and 90ch only two items can appear, with one item taking up the whole of the final row (if the total number is odd).

超过 90ch 时,每行有 3 个项目。 小于90ch,有5个item,每行两个item,除了最后一行,被最后一个item全部占满

At more than 90ch, there are three items per row. At less than 90ch, there are 5 items, with two items per row except the last row, which is taken up entirely by the last item

图片说明:具有奇数索引的项,也是最后一项,可以通过连接两个伪选择器来表示::nth-child(odd):last-child

通过设计理想的元素尺寸并容忍合理的差异,您基本上可以消除@media断点。您的组件本质上处理自己的布局,无需手动干预。我们介绍的许多布局都精巧地运用了这一基本机制,让您可以更精确地控制放置和环绕。

By designing to ideal element dimensions, and tolerating reasonable variance, you can essentially do away with @media breakpoints. Your component handles its own layout, intrinsically, and without the need for manual intervention. Many of the layouts we’re covering finesse this basic mechanism to give you more precise control over placement and wrapping.

例如,我们可能想要创建一个经典的侧边栏布局,其中两个相邻元素之一具有固定宽度,而另一个(如果您愿意的话,主要元素)占据剩余的可用空间。这应该是响应式的,没有@media断点,我们应该能够设置一个基于容器的断点来将元素包装成垂直配置。

For instance, we might want to create a classic sidebar layout, wherein one of two adjacent elements has a fixed width, and the other—the principle element, if you will—takes up the rest of the available space. This should be responsive, without @media breakpoints, and we should be able to set a container based breakpoint for wrapping the elements into a vertical configuration.

解决方案

The solution

侧边栏布局以构成小型侧边栏的元素命名:两个相邻元素中较窄的一个。它是一种量子布局,同时存在于两种配置之一——水平和垂直——如下图所示。采用哪种配置在构思时是未知的,并且完全取决于将其放置在父容器中时提供的空间。

The Sidebar layout is named for the element that forms the diminutive sidebar: the narrower of two adjacent elements. It is a quantum layout, existing simultaneously in one of the two configurations—horizontal and vertical—illustrated below. Which configuration is adopted is not known at the time of conception, and is dependent entirely on the space it is afforded when placed within a parent container.

左侧配置处于宽上下文中,元素彼此相邻。 正确的配置是在一个狭窄的上下文中,元素在彼此之上和之下。

The left configuration is in a wide context and the elements are next to each other. The right configuration is in a narrow context and the elements are above and below each other.

在有足够空间的地方,这两个元素并排出现。至关重要的是,当两个元素相邻时,侧边栏的宽度是固定的,非侧边栏占据了剩余的可用空间。但是当两个元素环绕时,每个元素都会占用100%共享容器。

Where there is enough space, the two elements appear side-by-side. Critically, the sidebar’s width is fixed while the two elements are adjacent, and the non-sidebar takes up the rest of the available space. But when the two elements wrap, each takes up 100% of the shared container.

如何在某个点强制换行,我们很快就会谈到。首先,我们需要设置水平布局。

How to force wrapping at a certain point, we will come to shortly. First, we need to set up the horizontal layout.

.with-sidebar {
  display: flex;
  flex-wrap: wrap;
}

.sidebar {
  flex-basis: 20rem;
  flex-grow: 1;
}

.not-sidebar {
  flex-basis: 0;
  flex-grow: 999;
}

这里要理解的关键是可用空间的作用。因为.not-sidebar元素的flex-grow值非常高 ( 999),所以它占用了所有可用空间。元素的flex-basis.sidebar不计入可用空间,而是从总空间中减去,因此出现类似侧边栏的布局。非侧边栏实质上将侧边栏压缩到理想宽度。

The key thing to understand here is the role of available space. Because the .not-sidebar element’s flex-grow value is so high (999), it takes up all the available space. The flex-basis value of the .sidebar element is not counted as available space and is subtracted from the total, hence the sidebar-like layout. The non-sidebar essentially squashes the sidebar down to its ideal width.

侧边栏宽度标记为 n。 伴随元素的宽度是所有可用空间,或空间减去 n。

The sidebar width is marked as n. The width of the accompanying element is all of the available space, or space minus n.

.sidebar元素在技术上仍然允许增长,并且能够.not-sidebar在其下方包裹的地方这样做。为了控制换行发生的位置,我们可以使用min-inline-size,这相当于min-width默认的horizontal-tb书写模式。

The .sidebar element is still technically allowed to grow, and is able to do so where .not-sidebar wraps beneath it. To control where that wrapping happens, we can use min-inline-size, which is equivalent to min-width in the default horizontal-tb writing mode.

.not-sidebar {
  flex-basis: 0;
  flex-grow: 999;
  min-inline-size: 50%;
}

.not-sidebar注定小于或等于50%容器宽度的地方,它被迫到一个新的行/行并增长以占用其所有空间。该值可以是任何值,但50%很合适,因为当侧边栏不再是两个元素中较窄的一个时,它就不再是侧边栏了。

Where .not-sidebar is destined to be less than or equal to 50% of the container’s width, it is forced onto a new line/row and grows to take up all of its space. The value can be anything, but 50% is apt since a sidebar ceases to be a sidebar when it is no longer the narrower of the two elements.

在左边,一个合法的侧边栏,其中伴随的元素比容器的 50% 宽。 在右侧,较窄的视口使其成为假侧边栏,因为伴随元素只占宽度的不到 50%。

On the left, a legitimate sidebar, where the accompanying element is wider than 50% of the container. On the right, a narrower viewport has made it a false sidebar, because the accompanying element takes up less than 50% of the width.

天沟

The gutter

到目前为止,我们将这两个元素视为相互接触。相反,我们可能想在它们之间放置一个装订线/空间。由于无论配置如何,我们都希望该空间出现在元素之间,并且我们希望外边缘有多余的边距,因此我们将像对Cluster layoutgap所做的那样使用该属性。

So far, we’re treating the two elements as if they’re touching. Instead, we might want to place a gutter/space between them. Since we want that space to appear between the elements regardless of the configuration and we don’t want there to be extraneous margins on the outer edges, we’ll use the gap property as we did for the Cluster layout.

对于 的装订线1rem,CSS 现在看起来如下所示。

For a gutter of 1rem, the CSS now looks like the following.

.with-sidebar {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
}

.sidebar {
  /* ↓ The width when the sidebar _is_ a sidebar */
  flex-basis: 20rem;
  flex-grow: 1;
}

.not-sidebar {
  /* ↓ Grow from nothing */
  flex-basis: 0;
  flex-grow: 999;
  /* ↓ Wrap when the elements are of equal width */
  min-inline-size: 50%;
}

此交互式演示仅在Every Layout站点上可用。

This interactive demo is only available on the Every Layout site.

固有侧边栏宽度

Intrinsic sidebar width

到目前为止,我们一直在规定侧边栏元素的宽度(flex-basis: 20rem在最后一个示例中)。相反,我们可能想让侧边栏的内容决定它的宽度。在我们根本不提供flex-basis值的情况下,侧边栏的宽度等于其内容的宽度。包装行为保持不变。

So far, we have been prescribing the width of our sidebar element (flex-basis: 20rem, in the last example). Instead, we might want to let the sidebar’s content determine its width. Where we do not provide a flex-basis value at all, the sidebar’s width is equal to the width of its contents. The wrapping behavior remains the same.

侧边栏显示为其中找到的图像的宽度

The sidebar is shown to be the width of the image found inside it

如果我们将侧边栏内图像的宽度设置为15rem,那将是水平配置中侧边栏的宽度。它将增长到100%垂直配置。

If we set the width of an image inside of our sidebar to 15rem, that will be the width of the sidebar in the horizontal configuration. It will grow to 100% in the vertical configuration.

用例

Use cases

侧边栏适用于各种内容。无处不在的“媒体对象”(将媒体项目放置在描述旁边)是支柱,但它也可用于将按钮与表单输入对齐(按钮形成侧边栏并具有内在的、基于内容的宽度)。

The Sidebar is applicable to all sorts of content. The ubiquitous “media object” (the placing of an item of media next to a description) is a mainstay, but it can also be used to align buttons with form inputs (where the button forms the sidebar and has an intrinsic, content-based width).

以下示例使用定义为自定义元素的组件版本。

The following example uses the component version, defined as a custom element.

<form>
  <sidebar-l side="right" space="0" contentMin="66.666%">
    <input type="text">
    <button>Search</button>
  </sidebar-l>
</form>

此交互式演示仅在Every Layout站点上可用。

This interactive demo is only available on the Every Layout site.

发电机

The generator

使用此工具生成基本的边栏CSS 和 HTML。

Use this tool to generate basic Sidebar CSS and HTML.

代码生成器工具仅在随附的文档站点中可用。这是基本的解决方案,带有注释。假定侧边栏是:last-child本例中的。

The code generator tool is only available in the accompanying documentation site. Here is the basic solution, with comments. It is assumed the non-sidebar is the :last-child in this example.

CSS

CSS

.with-sidebar {
  display: flex;
  flex-wrap: wrap;
  /* ↓ The default value is the first point on the modular scale */
  gap: var(--gutter, var(--s1));
}

.with-sidebar > :first-child {
  /* ↓ The width when the sidebar _is_ a sidebar */
  flex-basis: 20rem;
  flex-grow: 1;
}

.with-sidebar > :last-child {
  /* ↓ Grow from nothing */
  flex-basis: 0;
  flex-grow: 999;
  /* ↓ Wrap when the elements are of equal width */
  min-inline-size: 50%;
}

HTML

HTML

(您不必使用<div>s;在适当的地方使用语义元素。)

(You don’t have to use <div>s; use semantic elements where appropriate.)

<div class="with-sidebar">
  <div><!-- sidebar --></div>
  <div><!-- non-sidebar --></div>
</div>

组件

The component

Sidebar 的自定义元素实现可供下载

A custom element implementation of the Sidebar is available for download.

道具接口

Props API

以下道具(属性)将导致侧边栏组件在更改时重新呈现。它们可以在浏览器开发人员工具中手动更改,或者作为继承的应用程序状态的主题。

The following props (attributes) will cause the Sidebar component to re-render when altered. They can be altered by hand—in browser developer tools—or as the subjects of inherited application state.

姓名 类型 默认 描述
string "left" 将哪个元素视为侧边栏(除“左”之外的所有值都被视为“右”)
边宽 string 表示相邻侧边栏的宽度。如果未设置 ( null),则默认为侧边栏的内容宽度
最低含量 string "50%" CSS百分比值。水平配置中内容元素的最小宽度
空间 string "var(--s1)" 表示两个元素之间空间的 CSS 边距值
无拉伸 boolean false 使相邻元素采用它们的自然高度

例子

Examples

媒体对象

Media object

使用默认的50%“断点”和增加的space值,取自基于自定义属性的模块化比例尺。侧边栏/图像15rem在水平配置中很宽。

Uses the default 50% “breakpoint” and an increased space value, taken from the custom property-based modular scale. The sidebar/image is 15rem wide in the horizontal configuration.

因为图像是一个 flex 子对象,noStretch所以必须提供,以阻止它扭曲。如果图像被放置在一个<div>(使<div>flex 子节点)内,则没有必要。

Because the image is a flex child, noStretch must be supplied, to stop it distorting. If the image was placed inside a <div> (making the <div> the flex child) this would not be necessary.

<sidebar-l space="var(--s2)" sideWidth="15rem" noStretch>
  <img src="path/to/image" alt="Description of image" />
  <p><!-- the text accompanying the image --></p>
</sidebar-l>

交换媒体对象

Switched media object

与上一个示例相同,除了伴随图像的文本是侧边栏 ( side="right"),允许图像在布局处于水平配置时增长。侧边栏在水平配置中的宽度 ( measure ) 为(大约 30个<p>字符30ch

The same as the last example, except the text accompanying the image is the sidebar (side="right"), allowing the image to grow when the layout is in the horizontal configuration. The <p> sidebar has a width (measure) of 30ch (approximately 30 characters) in the horizontal configuration.

图像包含在 中,在这种情况下不需要<div>含义。noStretch图像应该增长以耗尽可用空间,因此响应图像的基本 CSS 应该在您的全局样式 ( img { max-width: 100% }) 中。

The image is contained in <div>, meaning noStretch is not necessary in this case. The image should grow to use up the available space, so the basic CSS for responsive images should be in your global styles (img { max-width: 100% }).

<sidebar-l space="var(--s2)" side="right" sideWidth="30ch">
  <div>
    <image src="path/to/image" alt="Description of image">
  </div>
  <p><!-- the text accompanying the image --></p>
</sidebar-l>

切换器

The Switcher

正如我们在Boxes中所述,最好提供建议而不是对视觉设计的布局方式发号施令。@media当我们尝试将设计固定到不同的环境和设备时,很容易过度使用断点。通过仅向浏览器建议它应该如何安排我们的布局框,我们从创建多个布局转变为同时存在于不同状态的单个量子布局。

As we set out in Boxes, it’s better to provide suggestions rather than diktats about the way the visual design is laid out. An overuse of @media breakpoints can easily come about when we try to fix designs to different contexts and devices. By only suggesting to the browser how it should arrange our layout boxes, we move from creating multiple layouts to single quantum layouts existing simultaneously in different states.

flex-basis在采用这种方法时,该属性是一个特别有用的工具。宣言的width: 20rem意思就是:20rem无论情况如何,都要扩大范围。但flex-basis: 20rem更微妙。它告诉浏览器将其视为20rem理想或“目标”宽度。然后20rem可以根据内容和可用空间自由计算目标与目标的相似程度。您授权浏览器为内容做出正确的决定,并授权用户根据他们的情况阅读该内容。

The flex-basis property is an especially useful tool when adopting such an approach. A declaration of width: 20rem means just that: make it 20rem wide — regardless of circumstance. But flex-basis: 20rem is more nuanced. It tells the browser to consider 20rem as an ideal or “target” width. It is then free to calculate just how closely the 20rem target can be resembled given the content and available space. You empower the browser to make the right decision for the content, and the user, reading that content, given their circumstances.

考虑以下代码。

Consider the following code.

.grid {
  display: flex;
  flex-wrap: wrap;
}

.grid > * {
  width: 33.333%;
}

@media (max-width: 60rem) {
  .grid > * {
    width: 50%;
  }
}

@media (max-width: 30rem) {
  .grid > * {
    width: 100%;
  }
}

这里的错误(除了没有使用 logical 属性inline-size代替width)是采用了一种外在的布局方法:我们首先考虑视口,然后调整我们的盒子以适应它。它冗长、不可靠,并且没有充分利用 Flexbox 的功能。

The mistake here (aside from not using the logical property inline-size in place of width) is to adopt an extrinsic approach to the layout: we are thinking about the viewport first, then adapting our boxes to it. It’s verbose, unreliable, and doesn’t make the most of Flexbox’s capabilities.

使用flex-basis,可以轻松制作响应式网格布局,无需@media断点干预。考虑这个替代代码:

With flex-basis, it's easy to make a responsive Grid-like layout which is in no need of @media breakpoint intervention. Consider this alternative code:

.grid {
  display: flex;
  flex-wrap: wrap;
}

.grid > * {
  flex: 1 1 20rem;
}

现在我在本质上思考——根据主题元素自身的维度。该flex速记属性翻译为“让每个元素增长和收缩以填充空间,但尽量使其20rem变宽”。我不是手动将列数与视口宽度配对,而是告诉浏览器根据我想要的列宽生成列。我已经自动化了我的布局。

Now I'm thinking intrinsically — in terms of the subject elements’ own dimensions. That flex shorthand property translates to "let each element grow and shrink to fill the space, but try to make it about 20rem wide". Instead of manually pairing the column count to the viewport width, I’m telling the browser to generate the columns based on my desired column width. I’ve automated my layout.

正如Zoe Mickley Gillenwater所指出的,与和flex-basis相结合,实现了类似于元素/容器查询的功能,因为根据可用空间而不是视口宽度隐式地发生“中断”。我的 Flexbox“网格”会根据放置它​​的容器的大小自动采用不同的布局。因此:量子布局flex-growflex-shrink

As Zoe Mickley Gillenwater has pointed out, flex-basis, in combination with flex-grow and flex-shrink, achieves something similar to an element/container query in that “breaks” occur, implicitly, according to the available space rather than the viewport width. My Flexbox “grid” will automatically adopt a different layout depending on the size of the container in which it is placed. Hence: quantum layout.

二维对称性问题

Issues with two-dimensional symmetry

虽然这是一种可用的布局机制,但它只会生成两个布局,其中每个元素的宽度相同:

While this is a serviceable layout mechanism, it only produces two layouts wherein each element is the same width:

  • 单列布局(给定最窄的容器)
  • 常规的多列布局(每行的列数相等)

在其他情况下,元素的数量和可用空间共同构成如下布局:

In other cases, the number of elements and the available space conspire to make layouts like these:

左边是两行元素的布局。 第一行有三个项目,第二行只有两个。 右边有一个类似的布局,除了最后一行只是一排长的项目

On the left is a layout of two rows of elements. The first row has three items and the second row has just two. On the right there is a similar layout, except the final row is just one row-long item

不一定是需要解决的问题,具体取决于简报。只要内容将自身配置为保留在空间中,不被遮挡,那么最重要的战斗就已经胜利了。但是,对于较少数量的主题元素,可能会出现您希望直接从水平(一行)布局切换到垂直(一列)布局并绕过中间状态的情况。

This is not necessarily a problem that needs to be solved, depending on the brief. So long as the content configures itself to remain in the space, unobscured, the most important battle has been won. However, for smaller numbers of subject elements, there may be cases where you wish to switch directly from a horizontal (one row) to a vertical (one column) layout and bypass the intermediary states.

任何已经包裹并成长为采用不同宽度的元素都可以被用户视为被“挑选出来”;故意看起来不同,或者更重要。我们应该避免这种混淆。

Any element that has wrapped and grown to adopt a different width could be perceived by the user as being “picked out”; made to deliberately look different, or more important. We should want to avoid this confusion.

该图显示了三个元素的水平线绕过中间布局(两个项目和第三个项目在其自己的行)以形成单列布局

Diagram shows a horizontal line of three elements bypassing an intermediate layout (of two items and the third item on its own row) to form a single-column layout

解决方案

The solution

Switcher元素(基于奇怪命名的Flexbox Holy Albatross )在给定的基于容器的断点处在水平和垂直布局之间切换 Flexbox 上下文。也就是说,如果断点是30rem,当父元素小于30remwide 时,布局将切换到垂直配置。

The Switcher element (based on the bizarrely named Flexbox Holy Albatross) switches a Flexbox context between a horizontal and a vertical layout at a given, container-based breakpoint. That is, if the breakpoint is 30rem, the layout will switch to a vertical configuration when the parent element is less than 30rem wide.

为了实现这种切换,首先要设置一个基本的水平布局,并flex-grow启用换行:

In order to achieve this switch, first a basic horizontal layout is instated, with wrapping and flex-grow enabled:

.switcher > * {
  display: flex;
  flex-wrap: wrap;
}

.switcher > * > * {
  flex-grow: 1;
}

flex-basis值将容器的(当前)宽度(表示为100%)输入到具有指定30rem断点的计算中。

The flex-basis value enters the (current) width of the container, expressed as 100%, into a calculation with the designated 30rem breakpoint.

30rem - 100%

根据 的解析值100%,这将返回正值负值:如果容器比 窄30rem,则返回正值;如果容器比 宽,则返回负值。然后将该数字乘以999产生一个非常大的正数或一个非常大的负数:

Depending on the parsed value of 100%, this will return either a positive or negative value: positive if the container is narrower than 30rem, or negative if it is wider. This number is then multiplied by 999 to produce either a very large positive number or a very large negative number:

(30rem - 100%) * 999

这是flex-basis原位声明:

Here is the flex-basis declaration in situ:

.switcher > * {
  display: flex;
  flex-wrap: wrap;
}

.switcher > * > * {
  flex-grow: 1;
  flex-basis: calc((30rem - 100%) * 999);
}

负值flex-basis无效,将被丢弃。由于 CSS 的弹性错误处理,这意味着仅flex-basis忽略该行,其余的 CSS 仍会应用。错误的负值flex-basis被更正为0——因为flex-grow存在——每个元素增长以占据相等比例的水平空间。

A negative flex-basis value is invalid, and dropped. Thanks to CSS’s resilient error handling this means just the flex-basis line is ignored, and the rest of the CSS is still applied. The erroneous negative flex-basis value is corrected to 0 and—because flex-grow is present—each element grows to take up an equal proportion of horizontal space.

另一方面,如果计算出的flex-basis值是一个很大的正数,则每个元素都会达到最大值以占据一整行。这导致垂直配置。中间配置被成功绕过。

If, on the other hand, the calculated flex-basis value is a large positive number, each element maxes out to take up a whole row. This results in the vertical configuration. Intermediary configurations are successfully bypassed.

该图显示 flex-basis 负 n 乘以 999 导致水平配置,正 n 乘以 999 导致垂直配置

Diagram shows that flex-basis negative n times 999 results in the horizontal configuration and positive n times 999 results in the vertical configuration

天沟

Gutters

为了支持主题元素之间的边距('gutters';'gaps'),我们可以采用Cluster文档中介绍的负边距技术。但是,flex-basis需要调整计算以补偿因拉伸父元素而增加的宽度。也就是说,通过在所有边上应用负边距,父级变得比它的容器更宽并且它们的100%值不再匹配。

To support margins ('gutters'; 'gaps') between the subject elements, we could adapt the negative margin technique covered in the Cluster documentation. However, the flex-basis calculation would need to be adapted to compensate for the increased width produced by stretching the parent element. That is, by applying negative margins on all sides, the parent becomes wider than its container and their 100% values no longer match.

.switcher {
  --threshold: 30rem;
  --space: 1rem;
}

.switcher > * {
  display: flex;
  flex-wrap: wrap;
  /* ↓ Multiply by -1 to make negative */
  margin: calc(var(--space) / 2 * -1);
}

.switcher > * > * {
  flex-grow: 1;
  flex-basis: calc((var(--threshold) - (100% - var(--space))) * 999);
  /* ↓ Half the value to each element, combining to make the whole */
  margin: calc(var(--space) / 2);
}

相反,由于gap现在所有主流浏览器都支持,我们不必再担心此类计算。该gap属性代表浏览器为我们进行此类计算。它使我们能够大大减少 HTML 和 CSS 代码。

Instead, since gap is now supported in all major browsers, we don’t have to worry about such calculations any more. The gap property represents the browser making such calculations for us. And it allows us to cut both the HTML and CSS code down quite a bit.

.switcher {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  --threshold: 30rem;
}

.switcher > * {
  flex-grow: 1;
  flex-basis: calc((var(--threshold) - 100%) * 999);
}

此交互式演示仅在Every Layout站点上可用。

This interactive demo is only available on the Every Layout site.

数量门槛

Quantity threshold

在水平配置中,分配给每个元素的空间量由两件事决定:

In the horizontal configuration, the amount of space alloted each element is determined by two things:

  • 总可用空间(容器的宽度)
  • 兄弟元素的数量

到目前为止,我的切换 根据可用空间进行切换。但是我们可以添加任意数量的元素,它们将在断点(或阈值)上方水平排列。我们添加的元素越多,分配给每个元素的空间就越少,事情很容易开始被压扁。

So far, my Switcher switches according to the available space. But we can add as many elements as we like, and they will lay out together horizontally above the breakpoint (or threshold). The more elements we add, the less space each gets alloted, and things can easily start to get squashed up.

这可以在文档中解决,或者通过在开发人员控制台中提供警告或错误消息来解决。但这不是很有效或健壮。最好布局自己处理这个问题。该项目中每个布局的目的是使它们尽可能自治。

This is something that could be addressed in documentation, or by providing warning or error messages in the developer's console. But that isn't very efficient or robust. Better to teach the layout to handle this problem itself. The aim for each of the layouts in this project is to make them as self-governing as possible.

很可能根据总共有多少兄弟元素来设置一组兄弟元素中的每一个元素的样式。该技术称为数量查询。考虑以下代码。

It is quite possible to style each of a group of sibling elements based on how many siblings there are in total. The technique is something called a quantity query. Consider the following code.

.switcher > :nth-last-child(n+5),
.switcher > :nth-last-child(n+5) ~ * {
  flex-basis: 100%;
}

在这里,我们对每个元素应用一个flex-basisof 100%,只有在总共有五个或更多元素的情况下。选择器以集合末尾nth-last-child(n+5)超过 4 个的任何元素为目标。然后,通用兄弟组合器 ( ) 将相同的规则应用于其余元素(它匹配 之后的任何内容)。如果少于 5 个项目,则没有元素并且不应用样式。~:nth-last-child(n+5):nth-last-child(n+5)

Here, we are applying a flex-basis of 100% to each element, only where there are five or more elements in total. The nth-last-child(n+5) selector targets any elements that are more than 4 from the end of the set. Then, the general sibling combinator (~) applies the same rule to the rest of the elements (it matches anything after :nth-last-child(n+5)). If there are fewer that 5 items, no :nth-last-child(n+5) elements and the style is not applied.

从右往回数,n + 5 匹配从倒数第 5 个元素开始的任何元素。 如果这些元素匹配,您可以使用它们通过通用兄弟 (~) 组合器选择所有其余元素。

Counting from the right back to the start, n + 5 matches any element starting at the 5th last element. If these elements are matched, you can use them to select all of the rest of the elements with the general sibling (~) combinator.

现在布局有两种它可以处理的阈值,因此它的鲁棒性是原来的两倍。

Now the layout has two kinds of threshold that it can handle, and is twice as robust as a result.

用例

Use cases

在许多情况下,您可能希望直接在水平和垂直布局之间切换。但它在每个元素都应被视为相等或连续体的一部分时特别有用。无论方向如何,广告产品的卡片组件都应共享相同的宽度,否则一张或多张卡片可能会以某种方式被视为突出显示或特色。

There are any number of situations in which you might want to switch directly between a horizontal and vertical layout. But it is especially useful where each element should be considered equal, or part of a continuum. Card components advertising products should share the same width no matter the orientation, otherwise one or more cards could be perceived as highlighted or featured in some way.

如果这些步骤沿着一条水平线或垂直线布置,则一组编号的步骤也更容易认知。

A set of numbered steps is also easier on cognition if those steps are laid out along one horizontal or vertical line.

从左到右或从上到下的编号步骤,但不能同时进行

Numbered steps going from either left to right or top to bottom, but not both at the same time

发电机

The Generator

代码生成器工具仅在随附的文档站点中可用。这是基本的解决方案,带有注释:

The code generator tool is only available in the accompanying documentation site. Here is the basic solution, with comments:

CSS

CSS

.switcher {
  display: flex;
  flex-wrap: wrap;
  /* ↓ The default value is the first point on the modular scale */
  gap: var(--gutter, var(--s1));
  /* ↓ The width at which the layout “breaks” */
  --threshold: 30rem;
}

.switcher > * {
  /* ↓ Allow children to grow */
  flex-grow: 1;
  /* ↓ Switch the layout at the --threshold */
  flex-basis: calc((var(--threshold) - 100%) * 999);
}

.switcher > :nth-last-child(n+5),
.switcher > :nth-last-child(n+5) ~ * {
  /* ↓ Switch to a vertical configuration if
  there are more than 4 child elements */
  flex-basis: 100%;
}

HTML

HTML

<div class="switcher">
  <div><!-- child element --></div>
  <div><!-- another child element --></div>
  <div><!-- etc --></div>
</div>

组件

The Component

切换器的自定义元素实现可供下载

A custom element implementation of the Switcher is available for download.

道具接口

Props API

以下道具(属性)将导致Switcher组件在更改时重新呈现。它们可以在浏览器开发人员工具中手动更改,或者作为继承的应用程序状态的主题。

The following props (attributes) will cause the Switcher component to re-render when altered. They can be altered by hand—in browser developer tools—or as the subjects of inherited application state.

姓名 类型 默认 描述
临界点 string "var(--measure)" 一个 CSSwidth值(代表“容器断点”)
空间 string "var(--s1)" 一个 CSSmargin
限制 integer 4 表示水平布局允许的最大项目数的数字

封面

The Cover

问题

The problem

多年来,人们对使用 CSS 水平和垂直居中有多么困难感到震惊。它被 CSS 的批评者用作其缺点的一种示范性“证明”。

For years, there was consternation about how hard it was to horizontally and vertically center something with CSS. It was used by detractors of CSS as a kind of exemplary “proof” of its shortcomings.

事实上,有很多方法可以让 CSS 内容居中。然而,只有这么多方法可以做到这一点,而不必担心溢出、重叠或其他类似的破损。例如,我们可以使用relative定位和 atransform来垂直居中父元素中的元素:

The truth is, there are numerous ways to center content with CSS. However, there are only so many ways you can do it without fear of overflows, overlaps, or other such breakages. For example, we could use relative positioning and a transform to vertically center an element within a parent:

.parent {
  /* ↓ Give the parent the height of the viewport */
  block-size: 100vh;
}

.parent > .child {
  position: relative;
  /* ↓ Push the element down 50% of the parent */
  inset-block-start: 50%;
  /* ↓ Then adjust it by 50% of its own height */
  transform: translateY(-50%);
}

translateY(-50%)部分的巧妙之处在于,它补偿了元素本身的高度——无论该高度是多少。不足之处是当子元素的内容使其比父元素高时产生的顶部和底部溢出。到目前为止,我们还没有设计出可以容忍动态内容的布局。

What’s neat about this is the translateY(-50%) part, which compensates for the height of the element itself—no matter what that height is. What’s less than neat is the top and bottom overflow produced when the child element's content makes it taller than the parent. We have not, so far, designed the layout to tolerate dynamic content.

在左侧,子元素比容器短,因此适合其垂直中心。 在右边,它更高。 它仍然居中,但突破了父级的顶部和底部边缘并溢出。

On the left, the child element is shorter than the container, so fits in its vertical center. On the right, it is taller. It is still central, but breaches the top and bottom edges of the parent and overflows.

也许最稳健的方法是结合 Flexbox 的justify-content: center(水平)和align-items: center(垂直)。

Perhaps the most robust method is to combine Flexbox’s justify-content: center (horizontal) and align-items: center (vertical).

.centered {
  display: flex;
  justify-content: center;
  align-items: center;
}

正确处理高度

Proper handling of height

仅应用 Flexbox CSS 本身不会对垂直居中产生明显的影响,因为默认情况下,.centered元素的高度由其内容的高度决定(隐含地,block-size: auto)。这有时被称为intrinsic sizing ,并且在边栏布局文档中有更详细的介绍。

Just applying the Flexbox CSS will not, on its own, have a visible effect on vertical centering because, by default, the .centered element’s height is determined by the height of its content (implicitly, block-size: auto). This is something sometimes referred to as intrinsic sizing, and is covered in more detail in the Sidebar layout documentation.

设置一个固定的高度——就像之前那个不可靠的transform例子一样——是有勇无谋的:我们事先不知道会有多少内容,或者它会占用多少垂直空间。换句话说,没有什么可以阻止溢出的发生。

Setting a fixed height—as in the unreliable transform example from before—would be foolhardy: we don’t know ahead of time how much content there will be, or how much vertical space it will take up. In other words, there’s nothing stopping overflow from happening.

在左侧,子元素位于 100vh 高父元素的垂直中心。 在右侧,元素高于 100vh 并突破底部边缘溢出。

On the left, the child element is in the vertical center of the 100vh high parent. On the right, the element is taller than 100vh and breaches the bottom edge to overflow.

相反,我们可以设置一个min-block-size(min-heighthorizontal-tb书写模式下)。这样,元素将垂直扩展以容纳内容,无论自然 ( auto) 高度恰好大于min-block-size. 在这种情况下,提供一些垂直填充可确保居中内容不与边缘相交。

Instead, we can set a min-block-size (min-height in the horizontal-tb writing mode). This way, the element will expand vertically to accommodate the content, wherever the natural (auto) height happens to be more than the min-block-size. Where this happens, the provision of some vertical padding ensures the centered content does not meet the edges.

在左侧,子元素没有父元素的最小高度高,因此出现在垂直居中。 在右侧,父元素已超过其最小高度以容纳更高的子元素。

On the left, the child element is not as tall as the parent’s min-height, so appears in the vertical center. On the right, the parent element has grown past its min-height to accommodate a taller child.

这在只有一个居中元素存在争议的情况下非常有用。但是我们习惯于在居中元素的上方和下方包含其他元素。也许它是右上角的关闭按钮,或者底部中心的“阅读更多”指示器。无论如何,我需要以模块化的方式处理这些情况,并且不会产生破损。

This is perfectly serviceable where only one centered element is in contention. But we have a habit of wanting to include other elements, above and below the centered one. Perhaps it's a close button in the top right, or a “read more” indicator in the bottom center. In any case, I need to handle these cases in a modular fashion, and without producing breakages.

解决方案

The solution

我需要的是一个布局组件,它可以处理垂直居中的内容(低于min-block-size阈值)并且可以容纳顶部/页眉和底部/页脚元素。为了使组件真正可组合,我应该能够在 HTML 中添加和删除这些元素,而不必同时调整 CSS。它应该是模块化的,因此不是对内容编辑器的编码强加。

What I need is a layout component that can handle vertically centered content (under a min-block-size threshold) and can accommodate top/header and bottom/footer elements. To make the component truly composable I should be able to add and remove these elements in the HTML without having to also adapt the CSS. It should be modular, and therefore not a coding imposition on content editors.

Cover组件是一个带有flex-direction: column. 此声明意味着子元素是垂直放置的,而不是水平放置的。换句话说,Flexbox 格式化上下文的“流向”返回到标准块元素的流向。

The Cover component is a Flexbox context with flex-direction: column. This declaration means child elements are laid out vertically rather than horizontally. In other words, the 'flow direction' of the Flexbox formatting context is returned to that of a standard block element.

.cover {
  display: flex;
  flex-direction: column;
}

封面有一个主要元素,它应该始终被吸引到中心此外,它可以有一个顶部/页眉元素和/或一个底部/页脚元素。

The Cover has one principal element that should always gravitate towards the center. In addition, it can have one top/header element and/or one bottom/footer element.

四种可能的配置中的每一种。 在每种情况下,主要元素都位于中心。

Each of the four possible configurations. In each case, the principal element is in the center.

我们如何在不调整 CSS 的情况下管理所有这些情况?首先,我们给居中元素(h1在示例中,但它可以是任何元素)提供auto边距。这可以在一个声明中使用margin-block

How do we manage all these cases without having to adapt the CSS? First, we give the centered element (h1 in the example, but it can be any element) auto margins. This can be done in one declaration using margin-block:

.cover {
  display: flex;
  flex-direction: column;
}

.cover > h1 {
  margin-block: auto;
}

这些将元素离它上面和下面的任何东西,将它移动到可用空间的中心。至关重要的是,它将推离父元素的内边缘兄弟元素的顶部/底部边缘。

These push the element away from anything above and below it, moving it into the center of the available space. Critically, it will push off the inside edge of a parent or the top/bottom edge of a sibling element.

在左侧,垂直自动边距将子元素准确地放置在垂直中心。 在右侧,标题元素就位。 居中元素位于可用空间的垂直中心

On the left, vertical auto margins place the child element in the exact vertical center. On the right, a header element is in place. The centered element is in the vertical center of the available space

图片说明:请注意,在右侧配置中,居中元素位于可用空间的垂直中心。

min-block-size剩下的就是确保在违反阈值的(最多)三个子元素之间有空间。

All that remains is to ensure there is space between the (up to) three child elements where the min-block-size threshold has been breached.

页眉和页脚元素的边距很小。 随着主要(居中)元素的增长,这些边距使元素分开。

The header and footer elements have small margins. As the principal (centered) element grows, these margins keep the elements apart.

目前,auto利润率已经跌至零。由于我们无法输入调整边距auto的函数(无效),我们能做的最好的事情就是根据上下文添加到页眉和页脚元素。calc()calc(auto + 1rem)margin

Currently, the auto margins simply collapse down to nothing. Since we can’t enter auto into a calc() function to adapt the margin (calc(auto + 1rem) is invalid), the best we can do is to add margin to the header and footer elements contextually.

.cover > * {
  margin-block: 1rem;
}

.cover > h1 {
  margin-block: auto;
}

.cover > :first-child:not(h1) {
  margin-block-start: 0;
}

.cover > :last-child:not(h1) {
  margin-block-end: 0;
}

请注意,使用级联、特异性和否定来定位正确的元素。首先,我们使用通用子选择器将顶部和底部边距应用于所有子项。然后我们为要居中的 ( ) 元素覆盖它h1以实现auto边距。最后,当顶部和底部元素不是居中元素时,我们才使用该:not()函数从顶部和底部元素中删除无关的边距。如果有一个居中元素和一个页脚元素,但没有页眉元素,居中元素将是并且必须保留。:first-childmargin-block-start: auto

Note, the use of the cascade, specificity and negation to target the correct elements. First, we apply top and bottom margins to all the children, using a universal child selector. We then override this for the to-be-centered (h1) element to achieve the auto margins. Finally, we use the :not() function to remove extraneous margin from the top and bottom elements only if they are not the centered element. If there is a centered element and a footer element, but no header element, the centered element will be the :first-child and must retain margin-block-start: auto.

此交互式演示仅在Every Layout站点上可用。

This interactive demo is only available on the Every Layout site.

现在可以安全地在Cover容器内部使用padding. 无论存在一个、两个还是三个元素,间距现在都保持对称,并且我们的组件模块化而无需样式干预。

Now it is safe to add spacing around the inside of the Cover container using padding. Whether there are one, two or three elements present, spacing now remains symmetrical, and our component modular without styling intervention.

.cover {
  padding: 1rem;
  min-block-size: 100vh;
}

min-block-size设置为100vh,以便元素覆盖视口高度的 100%(因此得名)。但是,没有理由min-block-size不能将 设置为另一个值。100vh被认为是合理的 default ,并且是即将到来的自定义元素实现minHeight中prop的默认值。

The min-block-size is set to 100vh, so that the element covers 100% of the viewport's height (hence the name). However, there's no reason why the min-block-size cannot be set to another value. 100vh is considered a sensible default, and is the default value for the minHeight prop in the custom element implementation to come.

水平居中

Horizontal centering

到目前为止,我还没有解决水平居中问题,这是经过深思熟虑的。布局组件应该尝试只解决一个问题——模块化居中问题是一个特殊的问题。Center布局处理水平居中,可以与Cover组合使用。您可以将Cover包裹在一个Center中,或者使一个Center成为它的一个或多个子对象。一切都与构图有关。

So far I've not tackled horizontal centering, and that's quite deliberate. Layout components should try to solve just one problem—and the modular centering problem is a peculiar one. The Center layout handles horizontal centering and can be used in composition with the Cover. You might wrap the Cover in a Center or make a Center one or more of its children. It's all about composition.

用例

Use cases

Cover的典型用途是为网页创建“首屏”介绍性内容。在下面的演示中,嵌套的Cluster元素用于布置徽标和导航菜单。在这种情况下,实用程序类 ( .text-align\:center) 用于水平居中 the<h1>和 footer 元素。

A typical use for the Cover would be to create the “above the fold” introductory content for a web page. In the following demo, a nested Cluster element is used to lay out the logo and navigation menu. In this case, a utility class (.text-align\:center) is used to horizontally center the <h1> and footer elements.

此交互式演示仅在Every Layout站点上可用。

This interactive demo is only available on the Every Layout site.

可能是您将页面的每个部分都视为一个Cover,并使用 Intersection Observer API 在封面进入视图时对其各个方面进行动画处理。下面提供了一个简单的实现(当data-visible元素进入视图时添加属性)。

It might be that you treat each section of the page as a Cover, and use the Intersection Observer API to animate aspects of the cover as it comes into view. A simple implementation is provided below (where the data-visible attribute is added as the element comes into view).

if ('IntersectionObserver' in window) {
  const targets = Array.from(document.querySelectorAll('cover-l'));
  targets.forEach(t => t.setAttribute('data-observe', ''));
  const callback = (entries, observer) => {
    entries.forEach(entry => {
      entry.target.setAttribute('data-visible', entry.isIntersecting);
    });
  };

  const observer = new IntersectionObserver(callback);
  targets.forEach(t => observer.observe(t));
}

发电机

The generator

使用此工具生成基本的Cover CSS 和 HTML。

Use this tool to generate basic Cover CSS and HTML.

代码生成器工具仅在随附的文档站点中可用。这是基本的解决方案,带有注释。它假定居中元素是<h1>, 在这种情况下,但它可以是任何元素。

The code generator tool is only available in the accompanying documentation site. Here is the basic solution, with comments. It assumes the centered element is an <h1>, in this case, but it could be any element.

CSS

CSS

.cover {
  --space: var(--s1);
  /* ↓ Establish a columnal flex context */
  display: flex;
  flex-direction: column;
  /* ↓ Set a minimum height to match the viewport height
  (any minimum would be fine) */
  min-block-size: 100vh;
  /* Set a padding value */
  padding: var(--space);
}

.cover > * {
  /* ↓ Give each child a top and bottom margin */
  margin-block: var(--s1);
}

.cover > :first-child:not(h1) {
  /* ↓ Remove the top margin from the first-child
  if it _doesn't_ match the centered element */
  margin-block-start: 0;
}

.cover > :last-child:not(h1) {
  /* ↓ Remove the bottom margin from the last-child
  if it _doesn't_ match the centered element */
  margin-block-end: 0;
}

.cover > h1 {
  /* ↓ Center the centered element (h1 here)
  in the available vertical space */
  margin-block: auto;
}

HTML

HTML

假设居中元素是<h1>, 并且在nth-child(2)position 中。

Assumes the centered element is an <h1>, and is in the nth-child(2) position.

<div class="cover">
  <div><!-- first child --></div>
  <h1><!-- centered child --></h1>
  <div><!-- third child --></div>
</div>

组件

The component

Cover 的自定义元素实现可供下载

A custom element implementation of the Cover is available for download.

道具接口

Props API

以下道具(属性)将导致Cover组件在更改时重新呈现。它们可以在浏览器开发人员工具中手动更改,或者作为继承的应用程序状态的主题。

The following props (attributes) will cause the Cover component to re-render when altered. They can be altered by hand—in browser developer tools—or as the subjects of inherited application state.

姓名 类型 默认 描述
居中 string "h1" 一个简单的选择器,例如元素或类选择器,代表封面中居中的(主)元素
空间 string "var(--s1)" 所有子元素之间和周围的最小空间
最小高度 string "100vh" Cover的最小高度(块大小)
无垫 boolean false 间距是否也作为填充应用到容器元素

例子

Examples

基本的

Basic

只是一个<h1>没有页眉或页脚伙伴的居中元素 (an )。context/parent 采用默认min-height100vh.

Just a centered element (an <h1>) with no header or footer companions. The context/parent adopts the default min-height of 100vh.

<cover-l>
  <h1>Welcome!</h1>
</cover-l>

网格

The Grid

问题

The problem

设计师有时会谈论设计到网格。他们首先将网格(水平和垂直线的矩阵)放置到位,然后填充该空间,使文字和图片跨越那些相交线创建的框。

Designers sometimes talk about designing to a grid. They put the grid—a matrix of horizontal and vertical lines—in place first, then they populate that space, making the words and pictures span the boxes those intersecting lines create.

不同形状的内容区域适合网格的不同区域

Different shaped areas of content fitting over different areas of a grid

“网格优先”的布局方法只有在提前知道两件事的情况下才真正可行:

A 'grid first' approach to layout is only really tenable where two things are known ahead of time:

  1. 空间
  2. 内容

对于以纸质为目标的杂志布局,如Axioms中描述的布局,这些都是可以实现的。对于包含动态(阅读:可变)内容的屏幕和设备无关的 Web 布局,它们根本不是。

For a paper-destined magazine layout, like the one described in Axioms, these things are attainable. For a screen and device-independent web layout containing dynamic (read: changeable) content, they fundamentally are not.

CSS Grid 模块是激进的,因为它允许您将内容放置在预定义网格中的任何位置,因此将设计带到web 的网格中。但是网格内容的放置越是讲究和深思熟虑,就越@media需要以断点的形式进行手动调整,以适应不同的空间布局。网格定义本身、内容在其中的位置,或者两者都必须手动更改,并使用额外的代码。

The CSS Grid module is radical because it lets you place content anywhere within a predefined grid, and as such brings designing to a grid to the web. But the more particular and deliberate the placement of grid content, the more manual adjustment, in the form of @media breakpoints, is needed to adapt the layout to different spaces. Either the grid definition itself, the position of content within it, or both will have to be changed by hand, and with additional code.

正如我在The Switcher中介绍的那样,@media断点仅与视口尺寸有关,与父容器提供的即时可用空间无关。这意味着使用断点定义的布局组件@media基本上不是上下文独立的:这对于模块化设计系统来说是一个巨大的问题。

As I covered in The Switcher, @media breakpoints pertain to viewport dimensions only, and not the immediate available space offered by a parent container. That means layout components defined using @media breakpoints are fundamentally not context independent: a huge issue for a modular design system.

即使在理论上,也不可能以上下文无关、自动响应的方式设计网格。但是,可以创建基本的网格状结构:分为列和行的元素集。

It is not, even theoretically, possible to design to a grid in a context-independent, automatically responsive fashion. However, it's possible to create basic grid-like formations: sets of elements divided into both columns and rows.

左侧:用标有“内容网格”的虚线标出的网格。 右侧:由标记为“内容网格”的黑框组成的网格

On the left: a grid marked out with dotted lines labeled 'a grid for content'. On the right: a grid made up of black boxes labeled 'a grid of content'

图片说明:Every Layout中,我们根据内容进行设计。没有内容,网格就不需要存在;有了内容,网格结构可能会从中出现

妥协是不可避免的,所以这是一个寻找最典型但最有效的解决方案的问题。

Compromise is inevitable, so it's a question of finding the most archetypal yet efficient solution.

网格的 Flexbox

Flexbox for grids

使用 Flexbox,我可以创建一个网格结构flex-basis来确定每个网格单元的理想宽度:

Using Flexbox, I can create a grid formation using flex-basis to determine an ideal width for each of the grid cells:

.flex-grid {
  display: flex;
  flex-wrap: wrap;
}

.flex-grid > * {
  flex: 1 1 30ch;
}

声明定义了Flexboxdisplay: flex上下文,flex-wrap: wrap允许换行,并flex: 1 1 30ch“理想的宽度应该是 30ch,但项目应该允许根据可用空间增长和收缩”。重要的是,列数不是根据固定的网格示意图规定的;它是根据可用空间和可用空间通过算法flex-basis确定的。内容和上下文定义网格,而不是人类仲裁者。

The display: flex declaration defines the Flexbox context, flex-wrap: wrap allows wrapping, and flex: 1 1 30ch says, "the ideal width should be 30ch, but items should be allowed to grow and shrink according to the space available". Importantly, the number of columns is not prescribed based on a fixed grid schematic; it's determined algorithmically based on the flex-basis and the available space. The content and the context define the grid, not a human arbiter.

The Switcher中,我们确定了包装增长之间的相互作用,导致项目在某些情况下“打破”网格形状:

In The Switcher, we identified an interaction between wrapping and growth that leads items to 'break' the grid shape under certain circumstances:

左边是两行元素的布局。 第一行有三个项目,第二行只有两个。 右边有一个类似的布局,除了最后一行只是一排长的项目

On the left is a layout of two rows of elements. The first row has three items and the second row has just two. On the right there is a similar layout, except the final row is just one row-long item

一方面,布局占据了容器的所有水平空间,并且没有难看的缝隙。另一方面,通用网格结构可能应该使其每个项目都与水平和垂直规则对齐。

On the one hand, the layout takes up all its container's horizontal space, and there are no unsightly gaps. On the other, a generic grid formation should probably make each of its items align to both the horizontal and vertical rules.

网格的网格

Grid for grids

恰当命名的 CSS Grid 模块在一种特定意义上使我们更接近“真正的”响应式网格结构:可以在破坏列边界的情况下使项目增长、收缩和包裹在一起。

The aptly named CSS Grid module brings us closer to a 'true' responsive grid formation in one specific sense: It's possible to make items grow, shrink, and wrap together without breaching the column boundaries.

flex 版本的垂直间距不是连续的,因为列会增长到不同的宽度。 网格版本确实具有等宽的列,但这会导致单元格太少而无法完成网格的间隙

The flex version's vertical gutters are not continuous, because the columns grow to be different widths. The grid version does have equal width columns, but this results in gaps where there are too few cells to complete the grid

这种行为更接近我心目中的原型响应式网格,并且将是我们在这里追求的布局。只有一个主要的实施问题需要解决。考虑以下代码。

This behavior is closer to the archetypal responsive grid I have in mind, and will be the layout we pursue here. There's just one major implementation issue to quash. Consider the following code.

.grid {
  display: grid;
  grid-gap: 1rem;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
}

这是我在 Jen Simmon 的Layout Land视频系列中首次发现的模式。分解它:

This is the pattern, which I first discovered in Jen Simmon's Layout Land video series. To break it down:

  1. display: grid:设置网格上下文,为它的孩子创建网格单元。
  2. grid-gap:在每个网格项之间放置一个“装订线” (使我们不必使用The Cluster中首次描述的负边距技术)。
  3. grid-template-columns:通常会为设计定义一个刚性网格,但与repeatauto-fit允许动态生成和环绕列以创建类似于前面的 Flexbox 解决方案的行为。
  4. minmax此函数确保每一列,因此内容的每个单元格共享最小值和最大值之间的宽度。由于1fr代表可用空间的一部分,列一起增长以填充容器。

这种布局的缺点是最小值在minmax(). 与flex-basis允许从单个“理想”值增加或减少任意数量的 不同,它minmax()设置了具有硬性限制的范围。

The shortcoming of this layout is the minimum value in minmax(). Unlike flex-basis, which allows any amount of growing or shrinking from a single 'ideal' value, minmax() sets a scope with hard limits.

如果没有固定的最小值(250px,在这种情况下),则没有任何东西可以触发包装。的值0只会产生一行不断减小的宽度。但它是一个固定的最小值有一个明显的后果:在任何比最小值窄的上下文中,都会发生溢出。

Without a fixed minimum (250px, in this case) there's nothing to trigger the wrapping. A value of 0 would just produce one row of ever-diminishing widths. But it being a fixed minimum has a clear consequence: in any context narrower than the minimum, overflow will occur.

单列框超出了浏览器视口的右边缘

Single column of boxes breaches the right hand edge of the browser viewport

简而言之:目前的模式只能安全地生成列收敛于低于容器估计最小宽度的布局。About250px是相当安全的,因为大多数手持设备的视口并不宽。但是,如果我希望我的列在可用空间的情况下大大超过这个宽度怎么办?使用 Flexboxflex-basis是很有可能的,但使用 CSS Grid 也不是没有帮助。

To put it simply: the pattern as it stands can only safely produce layouts where the columns converge on a width that is below the estimated minimum for the container. About 250px is reasonably safe because most handheld device viewports are no wider. But what if I want my columns to grow considerably beyond this width, where the space is available? With Flexbox and flex-basis that is quite possible, but with CSS Grid it is not without assistance.

解决方案

The solution

到目前为止,每个布局中描述的每个布局都只使用 CSS 处理了大小调整和包装,没有@media查询。有时不可能仅依靠 CSS 进行自动重新配置。在这些情况下,转向@media断点是不可能的,因为它破坏了布局系统的模块化。相反,我可能会遵从 JavaScript。但我应该明智地这样做,并使用渐进增强。

Each of the layouts described so far in Every Layout have handled sizing and wrapping with just CSS, and without @media queries. Sometimes it's not possible to rely on CSS alone for automatic reconfiguration. In these circumstances, turning to @media breakpoints is out of the question, because it undermines the modularity of the layout system. Instead, I might defer to JavaScript. But I should do so judiciously, and using progressive enhancement.

ResizeObserver(很快将在大多数现代浏览器中可用)是一个高度优化的 API,用于跟踪和响应元素维度的变化。这是迄今为止使用 JavaScript创建容器查询的最有效方法。我当然不建议使用它,但用于解决棘手的布局问题是可以接受的。

ResizeObserver (soon to be available in most modern browsers) is a highly optimized API for tracking and responding to changes in element dimensions. It is the most efficient method yet for creating container queries with JavaScript. I wouldn't recommend using it as a matter of course, but employed only for solving tricky layout issues is acceptable.

考虑以下代码:

Consider the following code:

.grid {
  display: grid;
  grid-gap: 1rem;
}

.grid.aboveMin {
  grid-template-columns: repeat(auto-fit, minmax(500px, 1fr));
}

该类aboveMin负责生成响应式网格的重写声明。ResizeObserver然后指示aboveMin根据容器宽度添加和删除类。500px(在上面的示例中)的最小值适用于容器本身比该阈值宽的情况。这是激活ResizeObserver网格元素上的独立函数。

The aboveMin class presides over an overriding declaration that produces the responsive grid. ResizeObserver is then instructed to add and remove the aboveMin class depending on the container width. The minimum value of 500px (in the above example) is only applied where the container itself is wider than that threshold. Here is a standalone function to activate the ResizeObserver on a grid element.

function observeGrid(gridNode) {
  // Feature detect ResizeObserver
  if ('ResizeObserver' in window) {
    // Get the min value from data-min="[min]"
    const min = gridNode.dataset.min;
    // Create a proxy element to measure and convert
    // the `min` value (which might be em, rem, etc) to `px`
    const test = document.createElement('div');
    test.style.width = min;
    gridNode.appendChild(test);
    const minToPixels = test.offsetWidth;
    gridNode.removeChild(test);

    const ro = new ResizeObserver(entries => {
      for (let entry of entries) {
        // Get the element's current dimensions
        const cr = entry.contentRect;
        // `true` if the container is wider than the minimum
        const isWide = cr.width > minToPixels;
        // toggle the class conditionally
        gridNode.classList.toggle('aboveMin', isWide);
      }
    });

    ro.observe(gridNode);
  }
}

如果ResizeObserver不支持,则永久应用后备单列布局。为了简洁起见,这里包含了这个基本的回退,但您可以转而回退到上一节中介绍的可用但不完美的 Flexbox 解决方案。在任何情况下,都不会丢失或遮挡任何内容,并且您可以使用更大的minmax()最小值来形成更具表现力的网格。由于我们不再受制于绝对限制,我们可以开始使用相对单位

If ResizeObserver is not supported, the fallback one-column layout is applied perpetually. This basic fallback is included here for brevity, but you could instead fallback to the serviceable-but-imperfect Flexbox solution covered in the previous section. In any case, no content is lost or obscured, and you have the ability to use larger minmax() minimum values for more expressive grid formations. And since we're no longer bound to absolute limits, we can begin employing relative units.

在左侧,使用 minmax(250px, 1fr) 创建了一个网格结构。 在右侧,视口比 250px 窄,但单元格宽度为 100%,这意味着没有任何东西溢出。

On the left, a grid formation is created with minmax(250px, 1fr). On the right, the viewport is narrower that 250px, but the cells are 100% width, meaning nothing is overflowing.

这是一个示例初始化(为简洁起见省略了代码):

Here's an example initialization (code is elided for brevity):

<div class="grid" data-min="250px">
  <!-- Place children here -->
</div>

<script>
  const grid = document.querySelector('.grid');
  observeGrid(grid);
</script>

min()函数_

The min() function

虽然它值得介绍,ResizeObserver因为它在其他情况下可能对您很有用,但实际上不再需要它来解决这个特定问题。那是因为我们有最近被广泛采用的CSSmin()函数。很抱歉追逐白鹅,但事实上,我们毕竟可以在没有 JavaScript 的情况下编写这个布局。

While it is worth covering ResizeObserver because it may serve you well in other circumstances, it is actually no longer needed to solve this particular problem. That’s because we have the recently widely adopted CSS min() function. Sorry for the wild goose chase but we can, in fact, write this layout without JavaScript after all.

作为后备,我们将网格配置为单列。然后我们用它@supports来测试min()并从那里增强:

As a fallback, we configure the grid into a single column. Then we use @supports to test for min() and enhance from there:

.grid {
  display: grid;
  grid-gap: 1rem;
}

@supports (width: min(250px, 100%)) {
  .grid {
    grid-template-columns: repeat(auto-fit, minmax(min(250px, 100%), 1fr));
  }
}

工作方式是从一组逗号分隔值中min()计算最短长度。即:min(250px, 100%)将返回100%where 250pxevaluated as higher than the evaluated 100%。这个有用的小算法为我们决定宽度必须限制在的位置100%

The way min() works is it calculates the shortest length from a set of comma-separated values. That is: min(250px, 100%) would return 100% where 250px evaluates as higher than the evaluated 100%. This useful little algorithm decides for us where the width must be capped at 100%.

用例

Use cases

网格非常适合浏览永久链接或产品的预告片。我可以使用BoxStack快速组成一个卡片组件来容纳我的每个预告片。

Grids are great for browsing teasers for permalinks or products. I can quickly compose a card component to house each of my teasers using a Box and a Stack.

此交互式演示仅在Every Layout站点上可用。

This interactive demo is only available on the Every Layout site.

发电机

The generator

使用此工具生成基本的网格CSS 和 HTML。

Use this tool to generate basic Grid CSS and HTML.

代码生成器工具仅在随附的文档站点中可用。这是基本的解决方案,带有注释:

The code generator tool is only available in the accompanying documentation site. Here is the basic solution, with comments:

CSS

CSS

.grid {
  /* ↓ Establish a grid context */
  display: grid;
  /* ↓ Set a gap between grid items  */
  grid-gap: 1rem;
  /* ↓ Set the minimum column width */
  --minimum: 20ch;
}

@supports (width: min(var(--minimum), 100%)) {
  .grid {
    /* ↓ Enhance with the min() function
    into multiple columns */
    grid-template-columns: repeat(auto-fit, minmax(min(var(--minimum), 100%), 1fr));
  }
}

HTML

HTML

<div class="grid">
  <div><!-- child element --></div>
  <div><!-- another child element --></div>
  <div><!-- etc --></div>
</div>

组件

The component

网格的自定义元素实现可供下载

A custom element implementation of the Grid is available for download.

道具接口

Props API

以下道具(属性)将导致Grid组件在更改时重新呈现。它们可以在浏览器开发人员工具中手动更改,或者作为继承的应用程序状态的主题。

The following props (attributes) will cause the Grid component to re-render when altered. They can be altered by hand—in browser developer tools—or as the subjects of inherited application state.

姓名 类型 默认 描述
分钟 string "250px" 表示 x in 的 CSS 长度值minmax(min(x, 100%), 1fr)
空间 string "var(--s1)" 网格单元之间的空间

例子

Examples

Cards

来自Use cases的卡片示例代码。请注意,该min值是标准测量值的一小部分。Axioms rudiment中有更多关于排版度量的内容。

The code for the cards example from Use cases. Note that the min value is a fraction of the standard measure. There's more on typographic measure in the Axioms rudiment.

<grid-l min="calc(var(--measure) / 3)">
  <box-l>
    <stack-l>
      <!-- card content -->
    </stack-l>
  </box-l>
  <box-l>
    <stack-l>
      <!-- card content -->
    </stack-l>
  </box-l>
  <box-l>
    <stack-l>
      <!-- card content -->
    </stack-l>
  </box-l>
  <box-l>
    <stack-l>
      <!-- card content -->
    </stack-l>
  </box-l>
  <!-- etc -->
</grid-l>

框架

The Frame

问题

The problem

有些事物以关系的形式存在。一条线作为两点之间的关系而存在;没有这两个点,就不能形成线。

Some things exist as relationships. A line exists as the relationship between two points; without both the points, the line cannot come into being.

说到画线,有些因素我们不一定知道,有些我们绝对知道。我们不一定知道在宇宙中每个点会出现在哪里。这可能不在我们的控制范围内。但我们确实知道,无论这些点出现在哪里,我们都能够在它们之间画一条直线。

When it comes to drawing lines, there are factors we don’t necessarily know, and others we absolutely do. We don’t necessarily know where, in the universe, each of the points will appear. That might be outside of our control. But we do know that, no matter where the points appear, we’ll be able to draw a straight line between them.

点对由线连接。 线相交。

Pairs of dots are joined by lines. The lines intersect.

图片说明:连接随机放置的成对的点会产生一些极其平淡无奇的生成艺术。

点的位置是可变的,但它们之间关系的性质是不变的。尽管存在变量,但利用存在的常量是我们塑造动态系统的方式。

The position of the points is variable, but the nature of their relationship is constant. Capitalizing on the constants that exist in spite of the variables is how we shape dynamic systems.

纵横比

Aspect ratio

宽高比是另一个经常出现的常量,尤其是在处理图像时。您可以通过将图像的宽度除以其高度来找到纵横比。

Aspect ratio is another constant that comes up a lot, especially when working with images. You find the aspect ratio by dividing the width of an image by its height.

16 x 9 的纵横比由 16 冒号 9、16 超过 9(粗俗分数)或 1.777 表示

A 16 by 9 aspect ratio represented by 16 colon 9, 16 over 9 (a vulgar fraction) or 1.777

<img />元素是一个被替换的元素;它是由它指向的外部加载源替换的元素。

The <img /> element is a replaced element; it is an element replaced by the externally loaded source to which it points.

作为 CSS 编写者,此源(图像文件,如 PNG、JPEG 或 SVG)具有您无法控制的某些特征。纵横比就是这样一种特性,它是在最初创建和裁剪图像时确定的。

This source (an image file such as a PNG, JPEG, or SVG) has certain characteristics outside of your control as a writer of CSS. Aspect ratio is one such characteristic, and is determined when the image is originally created and cropped.

让你的图像响应是确保它们不会溢出容器的问题。的max-inline-size100%就是这样做的。

Making your images responsive is a matter of ensuring they don’t overflow their container. A max-inline-size value of 100% does just that.

img {
  max-inline-size: 100%;
}

现在图像的宽度将匹配两个值之一:

Now the image’s width will match one of two values:

  • 它自己的固有/自然宽度,基于文件数据
  • 容器元素提供的水平空间的宽度

重要的是,无论哪种情况,高度都由纵横比决定。它与编写 相同block-size: auto,但现代兼容浏览器不需要显式声明。

Importantly, the height—in either case—is determined by the aspect ratio. It’s the same as writing block-size: auto, but that explicit declaration isn’t needed by modern, compliant browsers.

height == width / aspect ratio

有时我们想指定纵横比,而不是从图像文件中继承它。在不挤压或以其他方式扭曲图像的情况下实现此目的的唯一方法是动态重新裁剪图像。在图像上声明object-fit: cover将做到这一点:裁剪它以适应空间而不增加其自身的纵横比。容器成为未失真图像的窗口。

Sometimes we want to dictate the aspect ratio, rather than inheriting it from the image file. The only way to achieve this without squashing, or otherwise distorting, the image is to dynamically recrop it. Declaring object-fit: cover on an image will do just that: crop it to fit the space without augmenting its own aspect ratio. The container becomes a window onto the undistorted image.

16:9 帧放置在 24:9 图像上。 框架中没有间隙。

A 16:9 frame placed over a 24:9 image. There are no gaps in the frame.

可能有用的是一个通用的解决方案,我们可以根据给定的纵横比绘制一个矩形,并使其成为我们放置在其中的任何内容的窗口。

What might be useful is a general solution whereby we can draw a rectangle, based on a given aspect ratio, and make it a window onto any content we place within it.

解决方案

The solution

我们需要做的第一件事是找到一种方法来为任意元素指定纵横比,而无需对其宽度和高度进行硬编码。也就是说,我们需要让容器表现得像(替换的)图像。

The first thing we need to do is find a way to give an arbitrary element an aspect ratio without hard-coding its width and height. That is, we need to make a container behave like a (replaced) image.

为此,我们有aspect ratio 属性,它会取一个x/n值:

For that, we have the aspect ratio property that would take an x/n value:

.frame {
  aspect-ratio: 16 / 9;
}

在这个属性出现之前,我们不得不依赖于2009 年首次编写的内在比率技术。该技术利用了这样一个事实,即使在垂直维度上,填充也是相对于元素的宽度的。也就是说,padding-bottom: 56.25%将使一个空元素(没有设置高度)成为它宽度的十六分之九——纵横比为16:9. 您可以56.25%通过将9(表示高度)除以16(表示宽度)来找到 - 与找到纵横比本身相反的方法。

Before the advent of this property, we had to lean on an intrinsic ratio technique first written about as far back as 2009. The technique capitalizes on the fact that padding, even in the vertical dimension, is relative to the element’s width. That is, padding-bottom: 56.25% will make an empty element (with no set height) nine sixteenths as high as it is wide — an aspect ratio of 16:9. You find 56.25% by dividing 9 (representing the height) by 16 (representing the width) — the opposite way around to finding the aspect ratio itself.

66.666% 的底部填充是赋予元素形状的原因:纵横比为 6:9

The bottom padding of 66.666% is what gives the element its shape: an aspect ratio of 6:9

使用自定义属性 和calc(),我们可以创建一个接口,它接受比率的左(分子,或n)和右(分母,或d)值的任何数字:

Using custom properties and calc(), we can create an interface that accepts any numbers for the left (numerator, or n) and right (denominator, or d) values of the ratio:

.frame {
  padding-bottom: calc(var(--n) / var(--d) * 100%);
}

假设class="frame"块级元素(例如 a <div>),它的宽度将自动匹配其父元素的宽度。无论计算出的宽度值是多少,高度都是通过将其乘以 来确定的9 / 16

Assuming class="frame" is a block level element (such as a <div>), its width will automatically match that of its parent. Whatever the calculated width value, the height is determined by multiplying it by 9 / 16.

由于支持现在对aspect-ratioproperty有利,我们可以继续使用它而不是这个精心设计的 hack。

Since support is now good for the aspect-ratio property, we can go ahead and use that instead of this elaborate hack.

裁剪

Cropping

那么裁剪是如何工作的呢?对于替换元素,比如<img /><video />元素,我们只需要给它们一个100%宽度和高度,以及object-fit: cover

So how does the cropping work? For replaced elements, like <img /> and <video /> elements, we just need to give them a 100% width and height, along with object-fit: cover:

.frame {
  aspect-ratio: 16 / 9;
}

.frame > img,
.frame > video {
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
}

object-fit属性不是为普通的、不可替换的元素设计的,所以我们必须包含更多的东西来处理它们。幸运的是,Flexbox 对齐和对齐也有类似的效果。由于 Flexbox 对替换元素没有影响,我们可以安全地将这些样式添加到父元素,同时overflow: hidden防止内容转义。

The object-fit property is not designed for normal, non-replaced elements, so we’ll have to include something more to handle them. Fortunately, Flexbox justification and alignment can have a similar effect. Since Flexbox has no affect on replaced elements, we can safely add these styles to the parent, with overflow: hidden preventing the content from escaping.

.frame {
  aspect-ratio: 16 / 9;
  overflow: hidden;
  display: flex;
  justify-content: center;
  align-items: center;
}

.frame > img,
.frame > video {
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
}

现在,任何简单元素都将放置在Frame的中心,并在比Frame本身高或宽的地方裁剪。如果元素的内容使它比父元素高,它会在顶部底部被裁剪。由于内联内容换行,可能需要特定的设置宽度才能在左侧和右侧进行裁剪。为确保在所有上下文和所有缩放级别都发生裁剪,%基于 - 的值将起作用。

Now any simple element will be placed in the center of the Frame, and cropped where it is taller or wider than the Frame itself. If the element’s content makes it taller than the parent, it’ll be cropped at the top and the bottom. Since inline content wraps, a specific set width might be needed to cause cropping on the left and right. To make sure the cropping happens in all contexts, and at all zoom levels, a %-based value will work.

由于其内容使其比其父元素高,子元素在顶部和底部边缘被裁剪。 由于宽度为 150%,它在左右边缘被裁剪

The child element is cropped on the top and bottom edges due to its content making it taller than its parent. It’s cropped on the left and right edges due to a 150% width

用例

Use cases

Frame主要用于将媒体(视频和图像)裁剪成所需的纵横比。一旦开始控制宽高比,您当然可以根据当前情况对其进行调整。例如,您可能希望根据视口方向为图像提供不同的纵横比。

The Frame is mostly useful for cropping media (videos and images) to a desired aspect ratio. Once you start controlling the aspect ratio, you can of course tailor it to the current circumstances. For example, you might want to give images a different aspect ratio depending on the viewport orientation.

可以通过方向@media查询更改自定义属性值来实现此目的。在下面的示例中,上一个示例的框架元素被制作成方形(而不是16:9横向),其中有相对更多的垂直空间可用。

It’s possible to achieve this by changing the custom property values via an orientation @media query. In the following example, the Frame elements of the previous example are made square (rather than 16:9 landscape) where there is relatively more vertical space available.

@media (orientation: portrait) {
  .frame {
    aspect-ratio: 1 / 1;
  }
}

Flexbox 规定意味着您可以将任何类型的 HTML 裁剪为给定的宽高比,包括<canvas>元素,如果这些是您选择的创建图像的方式。一组类似卡片的组件可能每个都包含一个图像,或者在没有图像的情况下包含一个文本回退:

The Flexbox provision means you can crop any kind of HTML to the given aspect ratio, including <canvas> elements if those are your chosen means of creating imagery. A set of card-like components might each contain either an image or—where none is available—a textual fallback:

此交互式演示仅在Every Layout站点上可用。

This interactive demo is only available on the Every Layout site.

发电机

The generator

使用此工具生成基本的框架CSS 和 HTML。

Use this tool to generate basic Frame CSS and HTML.

代码生成器工具仅在随附站点中可用。这是基本的解决方案,带有注释。

The code generator tool is only available in the accompanying site. Here is the basic solution, with comments.

CSS

CSS

用您想要的任何值替换--n(分子)和--d(分母)值,以创建纵横比。

Replace the --n (numerator) and --d (denominator) values with whichever you wish, to create the aspect ratio.

.frame {
  --n: 16; /* numerator */
  --d: 9; /* denominator */
  aspect-ratio: var(--n) / var(--d);
  overflow: hidden;
  display: flex;
  justify-content: center;
  align-items: center;
}

.frame > img,
.frame > video {
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
}

HTML

HTML

以下示例使用图像。必须只有一个子元素,无论它是替换元素还是其他元素。

The following example uses an image. There must be just one child element, whether it is a replaced element or otherwise.

<div class="frame">
  <img src="/path/to/image" alt="description of the image here" />
</div>

组件

The component

框架的自定义元素实现可供下载

A custom element implementation of the Frame is available for download.

道具接口

Props API

以下道具(属性)将导致Frame组件在更改时重新渲染。它们可以在浏览器开发人员工具中手动更改,或者作为继承的应用程序状态的主题。

The following props (attributes) will cause the Frame component to re-render when altered. They can be altered by hand—in browser developer tools—or as the subjects of inherited application state.

姓名 类型 默认 描述
比率 string "16:9" 元素的纵横比

例子

Examples

图像框

Image frame

自定义元素采用ratio表达式,例如4:3(16:9是默认值)。

The custom element takes a ratio expression, like 4:3 (16:9 is the default).

<frame-l ratio="4:3">
  <img src="/path/to/image" alt="description of the image here" />
</frame-l>

卷轴

The Reel

问题

The problem

当我对音乐进行排序时,我不知道要创建的曲目要多长时间才能完成。当我添加声音条时,我的音序器软件知道这一点并按需提供时间。正如音乐音序器动态提供时间一样,网页提供空间。如果所有的歌曲都必须是四分二十六秒长,或者所有的网页768px都必须是高的,那么,那将是不必要的限制。

When I’m sequencing music, I don’t know how long the track I’m creating is going to be until I’m done. My sequencer software is aware of this and provisions time on demand, as I add bars of sound. Just as music sequencers dynamically provision time, web pages provision space. If all songs had to be four minutes and twenty six seconds long, or all web pages 768px high, well, that would be needlessly restrictive.

音乐音序器。 X 轴标记时间,Y 轴标记仪器

Music sequencer. X axis marks time and y axis marks instruments

可以在固定“视口”内探索提供的空间的机制称为滚动。没有它,每个人的设备都必须始终具有完全相同的尺寸、形状和放大倍数。为这样的空间写内容会变成一种形式主义的游戏,就像写俳句一样。多亏了滚动,您在编写 Web 内容时不必担心空间问题。为印刷品写作没有同样的奢侈。

The mechanism whereby the provisioned space can be explored within a fixed “viewport” is called scrolling. Without it, everyone’s devices would have to be exactly the same size, shape, and magnification level at all times. Writing content for such a space would become a formalist game, like writing haiku. Thanks to scrolling, you don’t have to worry about space when writing web content. Writing for print does not have the same luxury.

writing-mode您可能最熟悉的 CSS是horizontal-tb. 在这种模式下,文本和内联元素水平前进(从左到右,如英语,或从右到左),块元素从上到下流动(这就是tb部分)。由于指示文本和内联元素行,因此通常可以避免触发水平滚动的水平溢出。因为不允许内容向外伸,所以决心向下伸。块元素的垂直行进不可避免地触发垂直滚动。

The CSS writing-mode with which you are probably most familiar is horizontal-tb. In this mode, text and inline elements progress horizontally (either from left to right, as in English, or right to left) and block elements flow from top to bottom (that’s the tb part). Since text and inline elements are instructed to wrap, the horizontal overflow which would trigger horizontal scrolling is generally avoided. Because content is not permitted to reach outwards is resolves to reach downwards. The vertical progression of block elements inevitably triggers vertical scrolling instead.

块元素保持在其父元素的宽度内,但垂直溢出。 一个块元素显示在底部溢出视口。 出现了一个垂直滚动条。

Block elements stay within the width of their parent, but overflow vertically. One block element is shown overflowing the viewport at the bottom. A vertical scrollbar has appeared.

作为习惯了horizontal-tb书写模式的西方读者,垂直滚动是常规和意料之中的。当您发现页面需要垂直滚动才能看到所有内容时,您不会认为出了什么问题。在遇到水平滚动的地方,它不仅出乎意料,而且具有明显的可用性含义:在溢出遵循书写方向的地方,必须滚动每一行连续的文本才能阅读。

As a Western reader, accustomed to the horizontal-tb writing mode, vertical scrolling is conventional and expected. When you find the page needs to be scrolled vertically to see all the content, you don’t think something has gone wrong. Where you encounter horizontal scrolling, it’s not only unexpected but has clear usability implications: where overflow follows writing direction, each successive line of text has to be scrolled to be read.

所有这一切并不是说在horizontal-tb书写模式下水平滚动是被严格禁止的。事实上,在有意且清晰地实施的情况下,垂直滚动页面中的水平滚动部分可能是一种符合人体工程学的浏览内容的方式。例如,电视流媒体服务倾向于按垂直类别和水平节目来剖析其内容。您真正想要避免的一件事是双向滚动的单个元素。根据 WCAG 的1.4.10 回流标准,这被视为失败。

All this is not to say that horizontal scrolling is strictly forbidden within a horizontal-tb writing mode. In fact, where implemented deliberately and clearly, horizontally scrolling sections within a vertically scrolling page can be an ergonomic way to browse content. Television streaming services tend to dissect their content by category vertically and programme horizontally, for example. The one thing you really want to avoid are single elements that scroll bidirectionally. This is considered a failure under WCAG’s 1.4.10 Reflow criterion.

在左侧,页面垂直和水平滚动,并用十字标记为不良。 在右侧,页面垂直滚动,页面内的一些元素水平滚动,用勾号标记为良好。

On the left, the page scrolls vertically and horizontally, and is marked as bad with a cross. On the right, the page scrolls vertically and some elements inside the page scroll horizontally, which is marked as good with a tick.

我为 BBC 形式化了一个可访问的“旋转木马”组件——而不是完全遵从 JavaScript 的浏览功能——简单地调用带有溢出的本机滚动。提供的浏览按钮只是渐进增强,并增加滚动位置。每个 Layout 的Reel都是相似的,但放弃了 JavaScript 以单独依赖标准浏览器滚动行为。

I formalized an accessible “carousel” component for the BBC which—instead of deferring entirely to JavaScript for the browsing functionality—simply invokes native scrolling with overflow. The browsing buttons provided are merely a progressive enhancement, and increment the scroll position. Every Layout’s Reel is similar, but foregoes the JavaScript to rely on standard browser scrolling behavior alone.

解决方案

The solution

正如我们在The Cluster中所述,改变块流方向的有效方法是创建一个 Flexbox 上下文。通过应用display: flex到一个元素,它的子元素将从向下进行切换到向右进行——至少在默认的 LTR(从左到右)书写direction生效的地方是这样。

As we set out in The Cluster, an efficient way to change the direction of block flow is to create a Flexbox context. By applying display: flex to an element, its children will switch from progressing downwards to progressing rightwards — at least where the default LTR (left-to-right) writing direction is in effect.

通过省略经常补充的flex-wrap: wrap声明,元素被迫保持单一文件的形式。如果这行内容比父元素的宽度长,就会发生溢出。默认情况下,这将导致页面本身水平滚动。我们不希望这样,因为只有我们的 Flexbox 内容才真正需要滚动。其他一切都保持静止会更好。因此,我们改为应用于overflow: autoFlex 元素,它会自动调用该元素上的滚动,并且仅在确实发生溢出的地方调用。

By omitting the often complementary flex-wrap: wrap declaration, elements are forced to maintain a single-file formation. Where this line of content is longer than the parent element is wide, overflow occurs. By default, this will cause the page itself to scroll horizontally. We don’t want that, because it’s only our Flexbox content that actually needs scrolling. It would be better that everything else stays still. So, instead, we apply overflow: auto to the Flex element, which automatically invokes scrolling on that element and only where overflow does indeed occur.

.reel {
  display: flex;
  /* ↓ We only want horizontal scrolling */
  overflow-x: auto;
}

溢出右侧的容器会导致出现滚动条。

Overflowing the container on the right causes the scrollbar to appear.

我还没有解决可见性(使元素看起来可滚动),还有间距问题需要解决,但这是布局的核心。因为它利用了标准的浏览器行为,所以它的代码极其简洁且健壮——与普通的旋转木马/滑块 jQuery 插件完全不同。

I’m yet to tackle affordance (making the element look scrollable), and there’s the matter of spacing to address too, but this is the core of the layout. Because it capitalizes on standard browser behavior, it’s extremely terse in code and robust — quite unlike your average carousel/slider jQuery plugin.

滚动条

The scrollbar

滚动是多模式功能:有很多方法可以做到这一点,您可以选择最适合您的方法。虽然触摸、触控板手势和箭头键按下可能是一些更符合人体工程学的模式,但单击和拖动滚动条本身可能是最熟悉的,尤其是对于桌面上的老用户而言。有一个可见的滚动条有两个好处:

Scrolling is multimodal functionality: there are many ways to do it, and you can choose whichever suits you best. While touch, trackpad gestures, and arrow key presses may be some of the more ergonomic modes, clicking and dragging the scrollbar itself is probably the most familiar, especially to older users on desktop. Having a visible scrollbar has two advantages:

  1. 它允许通过拖动滚动条手柄(或“拇指”)进行滚动
  2. 它表示可以通过这种方式和其他方式进行滚动(增加可供性)

某些操作系统和浏览器默认隐藏滚动条,但有 CSS 方法可以显示它。Webkit 和基于 Blink 的浏览器提供以下前缀属性:

Some operating systems and browsers hide the scrollbar by default, but there are CSS methods to reveal it. Webkit and Blink-based browsers proffer the following prefixed properties:

::-webkit-scrollbar {
}
::-webkit-scrollbar-button {
}
::-webkit-scrollbar-track {
}
::-webkit-scrollbar-track-piece {
}
::-webkit-scrollbar-thumb {
}
::-webkit-scrollbar-corner {
}
::-webkit-resizer {
}

scrollbar-color从版本 64 开始,使用标准化和scrollbar-width属性在 Firefox 中设置滚动条样式的机会也有限。请注意,这些设置仅在“显示滚动条”设置为“始终”(在“设置”>“常规”scrollbar-color中)的 MacOS 上生效。

As of version 64, there are also limited opportunities to style the scrollbar in Firefox, with the standardized scrollbar-color and scrollbar-width properties. Note that the scrollbar-color settings only take effect on MacOS where Show scroll bars is set to Always (in Settings > General).

设置滚动条颜色是一个美学问题,这并不是每个布局的真正目的。但重要的是,出于可供性的原因,滚动条是明显的。选择以下黑白样式只是为了适合每个布局自己的审美。您可以根据需要调整它们。

Setting scrollbar colors is a question of aesthetics, which is not really what Every Layout is about. But it’s important, for reasons of affordance, that scrollbars are apparent. The following black and white styles are chosen just to suit Every Layout’s own aesthetic. You can adjust them as you wish.

.reel {
  display: flex;
  /* ↓ We only want horizontal scrolling */
  overflow-x: auto;
  /* ↓ First value: thumb; second value: track */
  scrollbar-color: var(--color-light) var(--color-dark);
}

.reel::-webkit-scrollbar {
  block-size: 1rem;
}

.reel::-webkit-scrollbar-track {
  background-color: var(--color-dark);
}

.reel::-webkit-scrollbar-thumb {
  background-color: var(--color-dark);
  background-image: linear-gradient(var(--color-dark) 0, var(--color-dark) 0.25rem, var(--color-light) 0.25rem, var(--color-light) 0.75rem, var(--color-dark) 0.75rem);
}

这些专有伪类并不支持所有属性。因此,在视觉上嵌入拇指是一个使用 绘制居中条纹的问题,linear-gradient而不是尝试使用边距或边框。

Not all properties are supported for these proprietary pseudo-classes. Hence, visually insetting the thumb is a question of painting a centered stripe using a linear-gradient rather than attempting a margin or border.

显示滚动条拇指如何具有从深到浅到深的渐变以使其看起来像是嵌入的。

Shows how the scrollbar thumb has a dark to light to dark gradient to make it look inset.

高度

Height

Reel实例的高度应该是多少?可能比视口短,以便可以看到整个Reel。但是我们应该设置一个高度吗?可能不会。最好的答案是“有多高就多高”,就是内容高度的问题。

What should the height of a Reel instance be? Probably shorter than the viewport, so that the whole Reel can be brought into view. But should we be setting a height at all? Probably not. The best answer is "as high as it needs to be", and is a question of the content height.

在下面的演示中,一个Reel元素包含一组类似卡片的组件。Reel的高度由最高卡片的高度决定,也就是内容最多的卡片。请注意,通过使用带有splitAfter="2".

In the following demonstration, a Reel element houses a set of card-like components. The height of the Reel is determined by the height of the tallest card, which is the card with the most content. Note that the last element of each “card” (a simple attribution) is pushed to the bottom of the space, by using a Stack with splitAfter="2".

此交互式演示仅在Every Layout站点上可用。

This interactive demo is only available on the Every Layout site.

对于可能非常大或使用不同宽高比的图像,我们可能需要设置Reel 的高度。普通图像block-sizeheighthorizontal-tb书写模式下)应相应地为100%,宽度为auto。这将确保图像共享一个高度但保持它们自己的纵横比。

For images, which may be very large or use differing aspect ratios, we may want to set the Reel’s height. The common image block-size (height in a horizontal-tb writing mode) should accordingly be 100%, and the width auto. This will ensure the images share a height but maintain their own aspect ratio.

.reel {
  block-size: 50vh;
}

.reel > img {
  block-size: 100%;
  width: auto;
}

此交互式演示仅在Every Layout站点上可用。

This interactive demo is only available on the Every Layout site.

间距

Spacing

隔开子元素曾经是一件非常棘手的事情。在这种情况下,在卷轴周围应用了边框,以赋予其形状。

Spacing out the child elements used to be a surprisingly tricky business. A border is applied around the Reel in this case, to give it its shape.

直到最近,我们还不得不使用 margin 和相邻兄弟组合器在子元素之间添加一个空格。我们使用一个逻辑属性来确保卷轴在两个书写方向上都能工作。

Until recently, we would have had to use margin and the adjacent sibling combinator to add a space between the child elements. We use a logical property to ensure the Reel works in both writing directions.

.reel > * + * {
  margin-inline-start: var(--s1);
}

现在,由于我们处于 Flexbox 上下文中,我们还可以使用gap应用于父级的属性:

Now, since we’re in a Flexbox context, we are also able to use the gap property, which is applied to the parent:

.reel {
  gap: var(--s1);
}

的主要优点gap是确保在元素换行时边距不会出现在错误的位置。由于Reel 的内容不是为了包装而设计的,因此我们将使用margin基于 的解决方案。它更长,更好地支持。

The main advantage of gap is ensuring the margins don’t appear in the wrong places when elements wrap. Since the Reel’s content is not designed to wrap, we shall use the margin-based solution instead. It’s longer and better supported.

在子元素周围(在它们和父元素之间)添加间距.reel是一件比较棘手的事情。不幸的是,padding 意外地与 scrolling 相互作用。右侧的效果就好像根本没有填充一样:

Adding spacing around the child elements (in between them and the parent .reel element) is a trickier business. Unfortunately, padding interacts unexpectedly with scrolling. The effect on the right hand side is as if there were no padding at all:

可滚动区域一直滚动到右侧。 尽管父元素上有填充,但最后一个子元素的右边缘与父元素的右边缘齐平

Scrollable area is scrolled all the way to the right. The last child element’s right edge is flush with the parent’s right edge despite the padding on the parent

所以,如果我们想在孩子周围留出空间,我们会采取不同的方法。我们为每个子元素的右侧添加边距,然后在最后一个子元素上使用伪内容插入空间。

So, if we want spacing around the children, we take a different approach. We add margin to all but the right hand side of each child element, then insert space using pseudo-content on the last of those children.

.reel {
  border-width: var(--border-thin);
}

.reel > * {
  margin: var(--s0);
  margin-inline-end: 0;
}

.reel::after {
  content: '';
  flex-basis: var(--s0);
  /* ↓ Default is 1 so needs to be overridden */
  flex-shrink: 0;
}

伪内容通过将最后一个子项的右外边缘移离父项的右内边缘来解决间距问题。

The pseudo-content fixes the spacing issue by moving the last child’s right outer edge away from the parent’s right inner edge.

下面的实现假定您不需要在Reel元素本身上填充;因此使用的方法.reel > * + *就足够了。

The implementation to follow assumes you do not need padding on the Reel element itself; the approach using .reel > * + * therefore suffices.

这只是留下了孩子和滚动条(存在和可见的地方)之间的空间来处理。class="reel"没问题,您可能会想:只需在父级(此处)的底部添加一些填充即可。问题是,这会在Reel未溢出且未调用滚动条的地方创建冗余空间。

That just leaves the space between the children and the scrollbar (where present and visible) to handle. Not a problem, you may think: just add some padding to the bottom of the parent (class="reel" here). The trouble is, this creates a redundant space wherever the Reel is not overflowing and the scrollbar has not been invoked.

理想情况下,会有一个用于溢出/滚动元素的伪类。然后我们可以有选择地添加填充。目前,:overflowed-content伪类只是一个想法而已。现在,我们可以使用 JavaScript 和一个简单的ResizeObserver. 从本质上讲,这是一种渐进式增强技术:在 JavaScript 不可用或ResizeObserver不受支持的情况下,填充不会出现在溢出的Reel中——但几乎没有不利影响。它只是将滚动条按在内容上。

Ideally, there would be a pseudo-class for overflowing/scrolling elements. Then we could add the padding selectively. Currently, the :overflowed-content pseudo-class exists as little more than an idea. For now, we can apply the margin, and remove it using JavaScript and a simple ResizeObserver. Innately, this is a progressive enhancement technique: where JavaScript is unavailable, or ResizeObserver is not supported, the padding does not appear for an overflowing Reel — but with little detrimental effect. It just presses the scrollbar up against the content.

const reels = Array.from(document.querySelectorAll('.reel'));
const toggleOverflowClass = elem => {
  elem.classList.toggle('overflowing', elem.scrollWidth > elem.clientWidth);
};

for (let reel of reels) {
  if ('ResizeObserver' in window) {
    new ResizeObserver(entries => {
      toggleOverflowClass(entries[0].target);
    }).observe(reel);
  }
}

在观察者内部,将卷轴 scrollWidth与其clientWidth. 如果scrollWidth较大,overflowing则添加该类。

Inside the observer, the Reel’s scrollWidth is compared to its clientWidth. If the scrollWidth is larger, the overflowing class is added.

.reel.overflowing {
  padding-block-end: var(--s0);
}

我们还没有完全完成,因为我们还没有处理从Reel中动态删除子元素的情况。那也会有影响scrollWidth。我们可以抽象类切换函数并添加一个MutationObserver观察childList. MutationObserver几乎得到无处不在的支持

We’re not quite done yet, because we haven’t dealt with the case of child elements being dynamically removed from the Reel. That will affect scrollWidth too. We can abstract the class toggling function and add a MutationObserver that observes the childList. MutationObserver is almost ubiquitously supported.

const reels = Array.from(document.querySelectorAll('.reel'));
const toggleOverflowClass = elem => {
  elem.classList.toggle('overflowing', elem.scrollWidth > elem.clientWidth);
};

for (let reel of reels) {
  if ('ResizeObserver' in window) {
    new ResizeObserver(entries => {
      toggleOverflowClass(entries[0].target);
    }).observe(reel);
  }

  if ('MutationObserver' in window) {
    new MutationObserver(entries => {
      toggleOverflowClass(entries[0].target);
    }).observe(reel, {childList: true});
  }
}

公平地说,如果仅用于添加或删除那一点填充,这有点矫枉过正。但是这些观察者可以用于其他增强功能,甚至超出样式。例如,对于键盘用户来说,溢出/可滚动的Reel采取tabindex="0"归属可能是有益的。这将使元素可以通过键盘聚焦,因此可以使用箭头键滚动。如果每个子元素都是可聚焦的,或者包含可聚焦的内容,则这可能不是必需的:聚焦元素会通过更改滚动位置自动将其带入视图。

It’s fair to say this is a bit overkill if only used to add or remove that bit of padding. But these observers can be used for other enhancements, even beyond styling. For example, it may be beneficial to keyboard users for an overflowing/scrollable Reel to take the tabindex="0" attribution. This would make the element focusable by keyboard and, therefore, scrollable using the arrow keys. If each child element is focusable, or includes focusable content, this may not be necessary: focusing an element automatically brings it into view by changing the scroll position.

用例

Use cases

Reel是旋转木马/滑块组件的强大而有效的解毒剂。正如已经讨论和演示的那样,它非常适合浏览事物类别:电影;产品; 新闻报道;照片。

The Reel is a robust and efficient antidote to carousel/slider components. As already discussed and demonstrated, it is ideal for browsing categories of things: movies; products; news stories; photographs.

此外,它还可用于取代按钮激活的菜单系统。对于许多人来说,Bradley Taunt 所说的香肠链接可能比“汉堡包”菜单更有用。对于这样的用例,可见滚动条可能相当笨拙。这就是为什么随后的自定义元素实现包含一个布尔noBar属性。

In addition, it can be used to supplant button-activated menu systems. What Bradley Taunt calls sausage links may well be more usable than “hamburger” menus for many. For such a use case, the visible scrollbar is probably rather heavy-handed. This is why the ensuing custom element implementation includes a Boolean noBar property.

此交互式演示仅在Every Layout站点上可用。

This interactive demo is only available on the Every Layout site.

当然,没有理由必须将链接做成香肠的形状!这只是一个词源宿醉。然而,需要注意的一件事是省略的滚动条所代表的缺乏启示。只要右侧最后一个可见的子元素被部分遮挡,就会相对清楚地发生溢出并且存在滚动能力。如果不是这种情况,则可能看起来所有可用元素都已在视图中。

There’s no reason why the links have to be shaped like sausages, of course! That’s just an etymological hangover. One thing to note, however, is the lack of affordance the omitted scrollbar represents. So long as the last visible child element on the right is partly obscured, it’s relatively clear overflow is occurring and the ability to scroll is present. If this is not the case, it may appear that all of the available elements are already in view.

在第一个版本中,最右边的元素被部分遮盖了。 标签上写着“我需要滚动”。 在第二个版本中,最右边的元素完全被遮住了。 标签上写着“一切似乎都在这里”。

In the first version, the right-most element is partly obscured. The label reads "I need to scroll". In the second version, the right-most element is completely obscured. The label reads "Everything seems to be here".

从布局的角度来看,您可以通过避免某些类型的宽度来减少“一切似乎都在这里”的可能性。25%像或这样的百分比宽度33.333%会产生问题,因为——至少在没有间距的情况下——这会使元素恰好位于空间内。

From a layout perspective, you can reduce the likelihood of "Everything seems to be here" by avoiding certain types of width. Percentage widths like 25% or 33.333% are going to be problematic because—at least in the absence of spacing—this will fit the elements exactly within the space.

此外,您可以通过其他方式指示滚动的可用性。您可以利用观察员的overflowing课程来揭示文本说明(也许阅读“滚动更多”):

In addition, you can indicate the availability of scrolling by other means. You can capitalize on the observers’ overflowing class to reveal a textual instruction (reading "scroll for more", perhaps):

.reel.overflowing + .instruction {
  display: block;
}

但是,这对当前滚动位置没有反应。您可以使用额外的脚本来检测元素何时一直滚动到任一侧,并相应地添加startend类。不断创新的Lea Verou 设计了一种仅使用图像和 CSS 来实现类似效果的方法。阴影背景图像占据background-attachment: scroll并保留在可滚动元素的两端。“影子封面”背景图片内容而background-attachment: local动。每当用户到达可滚动区域的一端时,这些“阴影覆盖”背景就会遮住下方的阴影。

However, this is not reactive to the current scroll position. You might use additional scripting to detect when the element is scrolled all the way to either side, and add start or end classes accordingly. The ever-innovative Lea Verou devised a way to achieve something similar using images and CSS alone. Shadow background images take background-attachment: scroll and remain at either end of the scrollable element. “Shadow cover” background images take background-attachment: local, moving with the content. Whenever the user reaches one end of the scrollable area, these “shadow cover” backgrounds obscure the shadows beneath them.

这些注意事项并不严格与布局相关,更多与通信相关,但值得进一步探索以提高可用性。

These considerations don’t strictly relate to layout, more to communication, but are worth exploring further to improve usability.

发电机

The generator

使用此工具生成基本的Reel CSS 和 HTML。您可能希望将ResizeObserver脚本与生成的代码一起包含在内。这是一个作为立即调用函数表达式 (IIFE) 实现的版本:

Use this tool to generate basic Reel CSS and HTML. You would want to include the ResizeObserver script along with the code generated. Here’s a version implemented as an Immediately Invoked Function Expression (IIFE):

(function() {
  const className = 'reel';
  const reels = Array.from(document.querySelectorAll(`.${className}`));
  const toggleOverflowClass = elem => {
    elem.classList.toggle('overflowing', elem.scrollWidth > elem.clientWidth);
  };

  for (let reel of reels) {
    if ('ResizeObserver' in window) {
      new ResizeObserver(entries => {
        toggleOverflowClass(entries[0].target);
      }).observe(reel);
    }

    if ('MutationObserver' in window) {
      new MutationObserver(entries => {
        toggleOverflowClass(entries[0].target);
      }).observe(reel, {childList: true});
    }
  }
})();

代码生成器工具仅在随附站点中可用。这是基本的解决方案,带有注释。为简洁起见,隐藏滚动条的代码已被删除,但可以通过自定义元素实现noBar中的属性获得。

The code generator tool is only available in the accompanying site. Here is the basic solution, with comments. The code that hides the scrollbar has been removed for brevity, but is available via the noBar property in the custom element implementation.

HTML

HTML

<div class="reel">
  <div><!-- child element --></div>
  <div><!-- another child element --></div>
  <div><!-- etc --></div>
  <div><!-- etc --></div>
</div>

CSS

CSS

.reel {
  /* ↓ Custom properties for ease of adjustment */
  --space: 1rem;
  --color-light: #fff;
  --color-dark: #000;
  --reel-height: auto;
  --item-width: 25ch;
  display: flex;
  block-size: var(--reel-height);
  /* ↓ Overflow */
  overflow-x: auto;
  overflow-y: hidden;
  /* ↓ For Firefox */
  scrollbar-color: var(--color-light) var(--color-dark);
}

reel-l::-webkit-scrollbar {
  /*
  ↓ Instead, you could make the scrollbar height
  a variable too. This is left as an exercise
  (be mindful of the linear-gradient!)
  */
  block-size: 1rem;
}

reel-l::-webkit-scrollbar-track {
  background-color: var(--color-dark);
}

reel-l::-webkit-scrollbar-thumb {
  background-color: var(--color-dark);
  /* ↓ Linear gradient ‘insets’ the white thumb within the black bar */
  background-image: linear-gradient(var(--color-dark) 0, var(--color-dark) 0.25rem, var(--color-light) 0.25rem, var(--color-light) 0.75rem, var(--color-dark) 0.75rem);
}

.reel > * {
  /*
  ↓ Just a `width` wouldn’t work because
  `flex-shrink: 1` (default) still applies
   */
  flex: 0 0 var(--item-width);
}

reel-l > img {
  /* ↓ Reset for images */
  block-size: 100%;
  flex-basis: auto;
  width: auto;
}

.reel > * + * {
  margin-inline-start: var(--space);
}

.reel.overflowing:not(.no-bar) {
  /* ↓ Only apply if there is a scrollbar (see the JavaScript) */
  padding-block-end: var(--space);
}

/* ↓ Hide scrollbar with `no-bar` class */
.reel.no-bar {
  scrollbar-width: none;
}

.reel.no-bar::-webkit-scrollbar {
  display: none;
}

JavaScript

JavaScript

只是一个立即调用的函数表达式(IIFE):

Just an Immediately Invoked Function Expression (IIFE):

(function() {
  const className = 'reel';
  const reels = Array.from(document.querySelectorAll(`.${className}`));
  const toggleOverflowClass = elem => {
    elem.classList.toggle('overflowing', elem.scrollWidth > elem.clientWidth);
  };

  for (let reel of reels) {
    if ('ResizeObserver' in window) {
      new ResizeObserver(entries => {
        for (let entry of entries) {
          toggleOverflowClass(entry.target);
        }
      }).observe(reel);
    }

    if ('MutationObserver' in window) {
      new MutationObserver(entries => {
        for (let entry of entries) {
          toggleOverflowClass(entry.target);
        }
      }).observe(reel, {childList: true});
    }
  }
})();

组件

The component

Reel 的自定义元素实现可供下载

A custom element implementation of the Reel is available for download.

道具接口

Props API

以下道具(属性)将导致Reel组件在更改时重新呈现。它们可以在浏览器开发人员工具中手动更改,或者作为继承的应用程序状态的主题。

The following props (attributes) will cause the Reel component to re-render when altered. They can be altered by hand—in browser developer tools—or as the subjects of inherited application state.

姓名 类型 默认 描述
项目宽度 string "auto" Reel 中每个项目(子元素)的宽度
空间 string "var(--s0)" 卷轴项目(子元素)之间的空间
高度 string "auto" 卷轴本身的高度
无酒吧 boolean false 是否显示滚动条

例子

Examples

卡滑块

Card slider

在这个例子中,卡片被赋予了20rem宽度。请注意,在这种情况下,“固定”宽度是可以接受的,因为水平空间是根据需要提供的,并且包装会处理文本和内联元素:允许卡片向下增长。

In this example, cards are given a 20rem width. Note that a “fixed” width is acceptable in this circumstance, since the horizontal space is provisioned as needed, and wrapping takes care of text and inline elements: the cards are allowed to grow downwards.

<reel-l itemWidth="20rem">
  <box-l>
    <stack-l>
      <!-- card content -->
    <stack-l>
  </box-l>
  <box-l>
    <stack-l>
      <!-- card content -->
    <stack-l>
  </box-l>
  <box-l>
    <stack-l>
      <!-- card content -->
    <stack-l>
  </box-l>
  <!-- ad infinitum -->
</reel-l>

冒名顶替者

The Imposter

问题

The problem

position在 CSS 中定位,使用属性的relativeabsolute和值的一个或多个实例fixed,就像手动覆盖 Web 布局一样。就是关掉自动布局,自己动手。与驾驶商用客机一样,除非在极少数和极端情况下,否则您不希望承担这种责任。

Positioning in CSS, using one or more instances of the position property’s relative, absolute, and fixed values, is like manually overriding web layout. It is to switch off automatic layout and take matters into your own hands. As with piloting a commercial airliner, this is not a responsibility you would wish to undertake except in rare and extreme circumstances.

Frame文档中,您被警告避开浏览器的标准布局算法的危险:

In the Frame documentation, you were warned of the perils of eschewing the browser’s standard layout algorithms:

当你给一个元素position: absolute时,你将它从文档的自然流中移除。它把自己布置得好像它周围的元素不存在一样。在大多数情况下,这是非常不受欢迎的,并且很容易导致内容重叠和模糊等问题。

但是,如果您通过在其上放置其他内容来遮盖内容怎么办?如果您在 Web 开发方面的工作时间超过 23 分钟,那么您很可能已经这样做过,包括对话框元素、“弹出窗口”或自定义下拉菜单。

But what if you wanted to obscure content, by placing other content over it? If you’ve been working in web development for more than 23 minutes, it’s likely you have already done this, in the incorporation of a dialog element, “popup”, or custom dropdown menu.

Imposter元素的目的是将通用叠加元素添加到您的布局套件中。它将允许作者将元素集中放置在视口、文档或选定的“定位容器”元素上。

The purpose of the Imposter element is to add a general purpose superimposition element to your layouts suite. It will allow the author to centrally position an element over the viewport, the document, or a selected “positioning container” element.

解决方案

The solution

有很多方法可以垂直居中放置元素,还有更多方法可以水平居中放置元素(一些替代方案作为中心布局的一部分被提及)。但是,只有几种方法可以将元素集中放置在其他元素/内容之上。

There are many ways to centrally position elements vertically, and many more to centrally position them horizontally (some alternatives were mentioned as part of the Center layout). However, there are only a few ways to position elements centrally over other elements/content.

同时期的方法是使用 CSS Grid。建立网格后,您可以按网格行号排列内容。流动的概念变得无关紧要,重叠在任何需要的地方都非常容易实现。

The contemporaneous approach is to use CSS Grid. Once your grid is established, you can arrange content by grid line number. The concept of flow is made irrelevant, and overlapping is eminently achievable wherever desired.

使用 grid-area: 2 / 2 / 5 / 8 将元素置于 Grid 的中心

An element is centered in a Grid using grid-area: 2 / 2 / 5 / 8

CSS Grid 并没有沉淀出一个通用的解决方案,因为它只有在你的定位元素display: grid提前设置好,并且列/行数合适的情况下才会起作用。我们需要更灵活的东西。

CSS Grid does not precipitate a general solution, because it would only work where your positioning element is set to display: grid ahead of time, and the column/row count is suitable. We need something more flexible.

定位

Positioning

您可以将元素定位到三个事物之一(从这里开始称为“定位上下文”):

You can position an element to one of three things (“positioning contexts” from here on):

  1. 视口
  2. 文档
  3. 一个祖先元素

要相对于视口定位元素,您可以使用position: fixed. 要相对于文档定位它,您可以使用position: absolute.

To position an element relative to the viewport, you would use position: fixed. To position it relative to the document, you use position: absolute.

相对于祖先元素定位它是可能的,该元素(从这里开始的“定位容器”)也被显式定位。最简单的方法是给祖先元素position: relative这会在不移动祖先元素位置或将其移出文档流的情况下设置本地化定位上下文。

Positioning it relative to an ancestor element is possible where that element (the “positioning container” from here on) is also explicitly positioned. The easiest way to do this is to give the ancestor element position: relative. This sets the localized positioning context without moving the position of the ancestor element, or taking it out of the document flow.

最外面的盒子被标记为定位容器。 内框标记为定位元素。

The very outer box is labeled the positioning container. The the inner box is labeled the positioned element.

该属性的static值为position默认值,因此您很少会看到或使用它,除非重置该值。

定心

Centering

我们如何将Imposter元素定位在文档、视口或定位容器的中心?对于定位元素,技术喜欢margin: autoplace-items: center不工作。在手动覆盖中,我们必须使用topleftbottom和/或right属性的组合。重要的是,这些属性中的每一个的值都与定位上下文相关,而不是与直接父元素相关。

How do we position the Imposter element over the center of the document, viewport, or positioning container? For positioned elements, techniques like margin: auto or place-items: center do not work. In manual override, we have to use a combination of the top, left, bottom, and/or right properties. Importantly, the values for each of these properties relate to the positioning context—not to the immediate parent element.

嵌套框。 最外面的盒子被标记为定位容器。 顶部的框与其他框重叠,标记为定位元素。 它的top和left offset是根据定位容器元素(外框)设置的

Nested boxes. The very outer box is labeled the positioning container. The box on top, overlapping the others is labeled the positioned element. Its top and left offset is set according to the positioning container element (the outer box)

该属性的static值为position默认值,因此您很少会看到或使用它。

到目前为止,太糟糕了:我们希望元素本身居中,而不是它的顶角。在我们知道width元素的地方,我们可以使用负边距进行补偿。例如,margin-inline-start: -20remand将使一个又宽又高margin-block-start: -10rem的元素重新居中(负值总是尺寸的一半)。40rem20rem

So far, so bad: we want the element itself to be centered, not its top corner. Where we know the width of the element, we can compensate by using negative margins. For example, margin-inline-start: -20rem and margin-block-start: -10rem will recenter an element that is 40rem wide and 20rem tall (the negative value is always half the dimension).

箭头指示向左和向上拉动元素的负边距,使其中心成为定位容器的中心

Arrows indicate the negative margins pulling the element left and up, to make its center the positioning container’s center

我们避免硬编码维度,因为它与定位一样,省去了浏览器根据可用空间排列元素的算法。无论您在元素的哪个位置编写固定宽度的代码,该元素或其内容在某个地方的某人设备上变得模糊的可能性几乎是不可避免的。不仅如此,我们可能无法提前知道元素的宽度或高度。所以我们不知道用哪个负边际值来补充它。

We avoid hard coding dimensions because, like positioning, it dispenses with the browser’s algorithms for arranging elements according to available space. Wherever you code a fixed width on an element, the chances of that element or its contents becoming obscured on somebody’s device somewhere are close to inevitable. Not only that, but we might not know the element’s width or height ahead of time. So we wouldn’t know which negative margin values with which to complement it.

我们不设计布局,而是为布局而设计让浏览器说了算。在这种情况下,这是使用转换的问题。该transform属性根据它们自己的尺寸排列元素,无论它们在给定时间是什么。简而言之:transform: translate(-50%, -50%)将元素的位置分别移动其自身宽度和高度的-50 %。我们不需要提前知道元素的尺寸,因为浏览器可以计算它们并为我们采取行动。

Instead of designing layout, we design for layout, allowing the browser to have the final say. In this case, it’s a question of using transforms. The transform property arranges elements according to their own dimensions, whatever they are at the given time. In short: transform: translate(-50%, -50%) will translate the element’s position by -50% of its own width and height respectively. We don’t need to know the element’s dimensions ahead of time, because the browser can calculate them and act on them for us.

因此,无论其尺寸如何,将元素置于其定位容器的中心都非常简单:

Centering the element over its positioning container, no matter its dimensions, is therefore quite simple:

.imposter {
  /* ↓ Position the top left corner in the center */
  position: absolute;
  inset-block-start: 50%;
  inset-inline-start: 50%;
  /* ↓ Reposition so the center of the element
  is the center of the positioning container */
  transform: translate(-50%, -50%);
}

此时需要注意的是,块级Imposter元素设置为position: absolute不再占用沿元素书写方向(通常是水平;从左到右)的可用空间。相反,元素“收缩包装”其内容,就好像它是内联的一样。

It should be noted at this point that a block-level Imposter element set to position: absolute no longer takes up the available space along the element’s writing direction (usually horizontal; left-to-right). Instead, the element “shrink wraps” its content as if it were inline.

带有文本 hello world 的小元素在其定位容器中居中

A small element with the text hello world is centered within its positioning container

默认情况下,元素的width大小为 50%,如果其内容占定位容器的 50% 以下,则元素的大小为 50%。如果你添加一个显式的widthor height,它会被接受并且元素将继续在定位容器中居中 - 内部翻译算法负责这一点。

By default, the element’s width will be 50%, or less if its content takes up less than 50% of the positioning container. If you add an explicit width or height, it will be honoured and the element will continue to be centered within the positioning container — the internal translation algorithm sees to that.

溢出

Overflow

如果定位的Imposter元素变得比其定位容器更宽或更高怎么办?通过仔细的设计和内容管理,您应该能够创建宽容度以防止这种情况在大多数情况下发生。但它仍有可能发生。

What if the positioned Imposter element becomes wider or taller than its positioning container? With careful design and content curation, you should be able to create the generous tolerances that prevent this from happening under most circumstances. But it may still happen.

默认情况下,效果会看到Imposter 从定位容器的边缘伸出来——并且可能有遮挡该容器周围内容的危险。在下图中,Imposter比其定位容器高。

By default, the effect will see the Imposter poking out over the edges of the positioning container — and may be in danger of obscuring content around that container. In the following illustration, an Imposter is taller than its positioning container.

在两个盒子中,上面的那个比下面的那个高,也比下面的低,这意味着它也与较短的盒子上方和下方的文本行重叠

Of two boxes, the one on top reaches higher and lower than the one underneath, meaning it also overlaps lines of text above and below the shorter box

由于max-widthand分别max-height覆盖widthand height,我们可以允许作者设置尺寸——或最小尺寸——但仍然确保元素被包含。剩下的就是添加overflow: auto以确保受限元素的内容可以滚动到视图中。

Since max-width and max-height override width and height respectively, we can allow authors to set dimensions—or minimum dimensions—but still ensure the element is contained. All that’s left is to add overflow: auto to ensure the constricted element’s contents can be scrolled into view.

.imposter {
  position: absolute;
  /* ↓ equivalent to `top` in horizontal-tb writing mode */
  inset-block-start: 50%;
  /* ↓ equivalent to `left` in horizontal-tb writing mode */
  inset-inline-start: 50%;
  transform: translate(-50%, -50%);
  /* ↓ equivalent to `max-width` in horizontal-tb writing mode */
  max-inline-size: 100%;
  /* ↓ equivalent to `max-height` in horizontal-tb writing mode */
  max-block-size: 100%;
}

利润

Margin

在某些情况下,需要在Imposter元素与其定位容器的内边缘之间有一个最小间隙(空格;边距;随便你怎么称呼它)。由于两个原因,我们无法通过在定位容器中添加 padding 来实现:

In some cases, it will be desirable to have a minimum gap (space; margin; whatever you want to call it) between the Imposter element and the inside edges of its positioning container. For two reasons, we can’t achieve this by adding padding to the positioning container:

  1. 它会插入容器的任何静态内容,这可能不是理想的视觉效果
  2. 绝对定位不考虑填充:我们的Imposter元素会忽略并重叠它

相反,答案是调整max-widthmax-height值。该calc()功能对于进行此类调整特别有用。

The answer, instead, is to adjust the max-width and max-height values. The calc() function is especially useful for making these kinds of adjustments.

.imposter {
  position: absolute;
  inset-block-start: 50%;
  inset-inline-start: 50%;
  transform: translate(-50%, -50%);
  max-inline-size: calc(100% - 2rem);
  max-block-size: calc(100% - 2rem);
}

上面的示例将创建1rem所有边的最小间隙:每个端的2rem值都被1rem删除。

The above example would create a minimum gap of 1rem on all sides: the 2rem value is 1rem removed for each end.

定位(冒名顶替)元素的顶部和底部边缘距离定位容器自身的内部边缘 1rem

The positioned (imposter) element’s top and bottom edges are 1rem away from the positioning container’s own inside edges

固定定位

Fixed positioning

如果您希望Imposter相对于viewport固定,而不是文档或文档中的元素(阅读:定位容器),您应该替换position: absoluteposition: fixed. 这对于对话框来说通常是可取的,它应该在用户滚动文档时跟随用户,并一直保持在视图中,直到被选中为止。

Where you wish the Imposter to be fixed relative to the viewport, rather than the document or an element (read: positioning container) within the document, you should replace position: absolute with position: fixed. This is often desirable for dialogs, which should follow the user as they scroll the document, and remain in view until tended to.

在以下示例中,Imposter元素具有--positioning默认值为 的自定义属性absolute

In the following example, the Imposter element has a --positioning custom property with a default value of absolute.

.imposter {
  position: var(--positioning, absolute);
  inset-block-start: 50%;
  inset-inline-start: 50%;
  transform: translate(-50%, -50%);
  max-inline-size: calc(100% - 2rem);
  max-block-size: calc(100% - 2rem);
}

正如Every Layout文章Dynamic CSS Components Without JavaScriptstyle中所述,您可以在特殊情况下的属性内内联覆盖此默认值:

As described in the Every Layout article Dynamic CSS Components Without JavaScript, you can override this default value inline, inside a style attribute for special cases:

<div class="imposter" style="--positioning: fixed">
  <!-- imposter content -->
</div>

在要遵循的自定义元素实现中(在The Component下),等效机制采用 Boolean fixedprop 的形式。添加fixed属性会覆盖absolute默认的定位。

In the custom element implementation to follow (under The Component) an equivalent mechanism takes the form of a Boolean fixed prop’. Adding the fixed attribute overrides the absolute positioning that is default.

用例

Use cases

任何需要故意模糊内容的地方,Imposter模式都是你的朋友。内容可能尚未提供。在这种情况下,冒名顶替者可能包含解锁该内容的号召性用语。

Wherever content needs to be deliberately obscured, the Imposter pattern is your friend. It may be that the content is yet to be made available. In which case, the Imposter may consist of a call-to-action to unlock that content.

此交互式演示仅在Every Layout站点上可用。

This interactive demo is only available on the Every Layout site.

可能是被Imposter遮挡的文物更具装饰性,不需要完整展示。

It may be that the artifacts obscured by the Imposter are more decorative, and do not need to be revealed in full.

使用Imposter创建对话框时,请注意需要包括的可访问性注意事项——尤其是那些与键盘焦点管理相关的注意事项。Inclusive Components有一个关于对话框的章节,详细描述了这些注意事项。

When creating a dialog using an Imposter, be wary of the accessibility considerations that need to be included—especially those relating to keyboard focus management. Inclusive Components has a chapter on dialogs which describes these considerations in detail.

发电机

The generator

使用此工具生成基本的Imposter CSS 和 HTML。

Use this tool to generate basic Imposter CSS and HTML.

代码生成器工具仅在随附的文档站点中可用。这是基本的解决方案,带有注释。该.contain版本在其定位容器中包含元素,并处理溢出。

The code generator tool is only available in the accompanying documentation site. Here is the basic solution, with comments. The .contain version contains the element within its positioning container, and handles overflow.

CSS

CSS

.imposter {
  /* ↓ Choose the positioning element */
  position: var(--positioning, absolute);
  /* ↓ Position the top left corner in the center */
  inset-block-start: 50%;
  inset-inline-start: 50%;
  /* ↓ Reposition so the center of the element
  is the center of the container */
  transform: translate(-50%, -50%);
}

.imposter.contain {
  /* ↓ Include a unit, or the calc function will be invalid */
  --margin: 0px;
  /* ↓ Provide scrollbars so content is not obscured */
  overflow: auto;
  /* ↓ Restrict the height and width, including optional
  spacing/margin between the element and positioning container */
  max-inline-size: calc(100% - (var(--margin) * 2));
  max-block-size: calc(100% - (var(--margin) * 2));
}

HTML

HTML

必须提供定位值为relativeor的祖先。absolute这成为放置.imposter元素的“定位容器”。在下面的示例中,使用了<div>带有position: relative内联样式的简单样式。

An ancestor with a positioning value of relative or absolute must be provided. This becomes the “positioning container” over which the .imposter element is positioned. In the following example, a simple <div> with the position: relative inline style is used.

<div style="position: relative">
  <p>Static content</p>
  <div class="imposter">
    <p>Superimposed content</p>
  </div>
</div>

组件

The component

Imposter 的自定义元素实现可供下载

A custom element implementation of the Imposter is available for download.

道具接口

Props API

以下道具(属性)将导致Imposter组件在更改时重新呈现。它们可以在浏览器开发人员工具中手动更改,或者作为继承的应用程序状态的主题。

The following props (attributes) will cause the Imposter component to re-render when altered. They can be altered by hand—in browser developer tools—or as the subjects of inherited application state.

姓名 类型 默认 描述
爆发 boolean false 是否允许元素脱离其所在的容器
利润 string 0 元素与放置它的定位容器的内边缘之间的最小空间(breakout未应用的地方)
固定的 boolean false 是否相对于视口定位元素

例子

Examples

演示示例

Demo example

用例部分中的演示代码。请注意在aria-hidden="true"叠加的同级内容上的使用。屏幕阅读器可能无法看到叠加的内容,因为它在视觉上不可用(或至少大部分被遮挡)。

The code for the demo in the Use cases section. Note the use of aria-hidden="true" on the superimposed sibling content. It’s likely the superimposed content should be unavailable to screen readers, since it is unavailable (or at least mostly obscured) visually.

<div style="position: relative">
  <text-l words="150" aria-hidden="true"></text-l>
  <imposter-l>
    <box-l style="background-color: var(--color-light)">
      <p class="h4"><strong>You can’t see all the content, because of this box.</strong></p>
    </box-l>
  </imposter-l>
</div>

对话

Dialog

Imposter元素可以采用 ARIA 属性作为role="dialog"屏幕阅读器中的对话框进行通信。或者,更简单地说,您可以<dialog>Imposter中放置一个。请注意,冒名顶替者fixed此处从一个位置切换到一个absolute位置fixed。这意味着当文档滚动时,对话框将保持在视口的中心。

The Imposter element could take the ARIA attribute role="dialog" to be communicated as a dialog in screen readers. Or, more simply, you could just place a <dialog> inside the Imposter. Note that the Imposter takes fixed here, to switch from an absolute to fixed position. This means the dialog would stay centered in the viewport as the document is scrolled.

<imposter-l fixed>
  <dialog aria-labelledby="message">
    <p id="message">It’s decision time, sunshine!</p>
    <button type="button">Yes</button>
    <button type="button">No</button>
  </dialog>
</imposter-l>

图标

The Icon

问题

The problem

Every Layout中的大多数布局都采用块组件的形式,如果您不介意的话。也就是说,它们设置了一个块级上下文,在其中它们会影响其控件中子元素的布局。正如您将在Boxes中发现的那样,显示值为blockflexgrid的元素本身是块级的(flex并且grid通过以特殊方式影响其子元素而有所不同)。

Most of the layouts in Every Layout take the form of block components, if you’ll excuse the expression. That is, they set a block-level context wherein they affect the layout of child elements in their control. As you will discover in Boxes, elements with display values of either block, flex, or grid are themselves block-level (flex and grid differ by affecting their child elements in a special way).

在这里,我们将看到更小的东西,它不会比图标小很多。这将是自定义元素将保留其默认inline显示模式的第一个布局。

Here, we shall be looking at something a lot smaller, and it doesn’t get much smaller than an icon. This will be the first layout for which the custom element will retain its default inline display mode.

让事物排列整齐并看起来正确,内联可能是一项不稳定的业务。当谈到图标时,我们不得不担心这样的事情:

Getting things to line up and look right, inline can be a precarious business. When it comes to icons, we have to worry about things like:

  • 图标与文字的距离
  • 图标的高度与文字的高度对比
  • 图标与文本的垂直对齐方式
  • 当文本出现图标之后而不是之前时会发生什么
  • 调整文本大小时会发生什么

一个简单的图标

A simple icon

在研究任何这些内容之前,我首先要为您提供 SVG 图像学的快速速成课程,因为 SVG 是网络上事实上的图像学格式。考虑以下代码:

Before looking into any of these, I’m first going to give you a really quick crash course in SVG iconography, since SVG is the de facto iconography format on the web. Consider the following code:

<svg viewBox="0 0 10 10" width="0.75em" height="0.75em" stroke="currentColor" stroke-width="2">
  <line x1="1" y1="1" x2="9" y2="9" />
  <line x1="9" y1="1" x2="1" y2="9" />
</svg>

这定义了一个简单的图标:十字。让我解释一下每个主要功能:

This defines a simple icon: a cross. Let me explain each of the key features:

  • viewBox:这定义了 SVG 的坐标系。该0 0部分表示“从左上角开始计数”“10 10”部分表示为 SVG“画布”提供 10 个水平坐标和 10 个垂直坐标。我们正在定义一个正方形,因为我们所有的图标都将占据一个正方形空间。
  • widthheight:设置图标的大小。我将解释为什么它使用这个单位,并很快em被设置为。0.75em现在,请注意我们在 SVG 上设置宽度和高度而不是在 CSS 中,因为即使 CSS 无法加载,我们也希望保持图标较小。默认情况下,SVG 在大多数浏览器中显示得非常大。
  • strokestroke-width:这些表示属性赋予<line />元素可见的形式。它们可以用 CSS 编写或覆盖。但是因为我们没有使用很多,所以最好确保这些也是独立于 CSS 的。
  • <line /><line />元素绘制一条简单的线。在这里,我们从左上角到右下角绘制了一个,然后从右上角到左下角绘制了一个(制作我们的十字架)。我正在使用1and 而9不是0and10来补偿该行stroke-width2. 否则该行会溢出 SVG“画布”。

左:用每个角的端点坐标标记的十字。 右:用笔划宽度和笔划颜色标记的十字。

Left: cross marked with the coordinates of the end points in each corner. Right: cross marked with the stroke width and stroke color.

有很多方法可以绘制相同的十字形状。也许最有效的方法是使用<path />元素。路径允许您将所有坐标放在一个d属性中。该M符号标记每条线的单独坐标的开始:

There are many ways to draw the same cross shape. Perhaps the most efficient is to use a <path /> element. A path lets you place all the coordinates in one d attribute. The M symbol marks the start of each line’s separate coordinates:

<svg viewBox="0 0 10 10" width="0.75em" height="0.75em" stroke="currentColor" stroke-width="2">
  <path d="M1,1 9,9 M9,1 1,9" />
</svg>

当您的 SVG 数据如此简洁时,没有理由不将其内联包含而不是使用<img />指向 SVGsrc文件的指针。

When your SVG data is this terse, there’s no reason not to include it inline rather than using an <img /> pointing to an SVG src file.

除了能够免除 HTTP 请求之外,还有其他优势,例如currentColor所示的使用能力。此关键字使您的内联 SVG 采用color周围文本的。对于接下来的演示图标,图标是通过 元素包含的,该元素引用单个icons.svg文件(以及 HTTP 请求)中定义<use>的许多图标之一。当以这种方式引用 SVG 数据时,该技术仍然有效。<symbol>currentColor

There are other advantages besides being able to dispense with an HTTP request, like the ability to use currentColor as shown. This keyword makes your inline SVG adopt the color of the surrounding text. For the demo icons to follow, the icons are included via the <use> element, which references one of many icon <symbol>s defined in a single icons.svg file (and therefore HTTP request). The currentColor technique still works when referencing SVG data in this way.

<svg class="icon">
  <use href="/images/icons/icons.svg#cross"></use>
</svg>

无论如何,SVG 是一种高效的可缩放格式,比 PNG 等光栅图像更适合图像学,并且没有图标字体伴随的可访问性问题

In any case, SVG is an efficient scalable format, much better suited to iconography than raster images like PNGs, and without the attendant accessibility issues of icon fonts.

我们的任务是为方形图标创建一个可靠的 SVG 画布,并确保它与文本无缝融合,只需最少的手动配置。

Our task here is to create a dependable SVG canvas for square icons, and ensure it fits seamlessly alongside text, with the minimum of manual configuration.

解决方案

The solution

垂直对齐

Vertical alignment

正如前面的注释currentColor所建议的那样,我们将像对待文本一样对待我们的图标,并让它们尽可能无缝地伴随文本。幸运的是,默认情况下 SVG 将位于文本的baseline上方,就好像它是一封信一样。

As the previous note on currentColor suggests, we are going to treat our icons like text, and get them to accompany text as seamlessly as possible. Fortunately, the SVG will sit on the text’s baseline by default, as if it were a letter.

对于更高的图标,您可能希望能够使用vertical-align: middle. 然而,与流行的看法相反,这并不是围绕字体的垂直中间对齐,而是围绕字体小写字母的垂直中间对齐。因此,结果可能是不理想的。

For taller icons, you may expect to be able to use vertical-align: middle. However, contrary to popular belief this does not align around the vertical middle of the font, but the vertical middle of the lowercase letters of the font. Hence, the result will probably be undesirable.

左侧:基线标记在字母的底部边缘。 右边:线被标记为平分小写字母。 因此,较大的十字图标位置太低。

On the left: Baseline marked at bottom edge of letters. On the right: line is marked bisecting the lowercase letters. The larger cross icon sits too low, consequently.

相反,为更高的图标调整垂直对齐可能是为vertical-align属性提供长度的问题。该长度表示基线以上的距离,可以取负值。

Instead, adjusting the vertical alignment for a taller icon will probably be a matter of supplying the vertical-align attribute with a length. This length represents the distance above the baseline, and can take a negative value.

较大的图标被向下拖动 0.125em

The larger icon is dragged down by 0.125em

对于我们的图标布局,我们将坚持在基线上放置图标。这是最稳健的方法,因为悬挂在基线下方的图标可能会与发生换行的连续文本行发生冲突。

For our Icon layout, we shall stick to sitting icons on the baseline. This is the most robust approach since icons that hang below the baseline may collide with a successive line of text where wrapping occurs.

匹配高度

Matching height

一个合适的图标高度,从基线开始,在某种程度上取决于字体的大小写和下部是否存在。如果字母都是小写的,并且包括后缀,那么事情看起来特别不平衡。

A suitable icon height, starting at the baseline, depends somewhat on the casing of the font and the presence or absence of descenders. Where the letters are all lowercase, and include descenders, things can look particularly unbalanced.

此交互式演示仅在Every Layout站点上可用。

This interactive demo is only available on the Every Layout site.

通过确保图标附带文本的首字母始终大写,并且图标本身是大写字母的高度,可以缓解这种感知问题。

This perceptual issue can be mitigated by ensuring the first letter of the icon’s accompanying text is always uppercase, and that the icon itself is the height of an uppercase letter.

实际上匹配每种字体的大写字母高度是另一回事。您可能希望1em成为价值,但这种情况很少见。1em更接近于字体本身的高度。通过从几种字体中选择文本,您会发现字体高度通常高于其大写字母。换句话说:1em对应于字体规格,而不是字母规格。

Actually matching the uppercase letter height per font is another matter. You might expect 1em to be the value, but that is rarely the case. 1em more closely matches the height of the font itself. By making selections of text from a few fonts, you’ll see the font height is often taller than its capital letters. To put it another way: 1em corresponds to font metrics, not letter metrics.

在我的实验中,我发现0.75em更接近匹配大写字母的高度。因此,我的十字图标的表示属性是0.75em每个,按照viewBox.

In my experimentation, I found that 0.75em more closely matches uppercase letter height. Hence, the presentation attributes for my cross icon being 0.75em each, to make a square following the precedent set by the viewBox.

<svg viewBox="0 0 10 10" width="0.75em" height="0.75em" stroke="currentColor" stroke-width="2">
  <path d="M1,1 9,9 M9,1 1,9" />
</svg>

显示每个图标与其大写字母高度匹配的流行字体

Popular fonts showing that each icon matches its uppercase letter height

从左到右:Arial、Georgia、Trebuchet 和 Verdana。对于每个图标,0.75em匹配大写字母高度。

然而,新兴cap单位承诺评估单个字体以获得更准确的匹配。由于它目前还没有得到很好的支持,我们可以0.75em在我们的 CSS 中使用它作为后备:

However, the emerging cap unit promises to evaluate the individual font for a more accurate match. Since it is currently not supported very well, we can use 0.75em as a fallback in our CSS:

.icon {
  height: 0.75em;
  height: 1cap;
  width: 0.75em;
  width: 1cap;
}

最好0.75em在 CSS 中也包含这些值,以防作者省略了表示属性。

Better to have the 0.75em values in the CSS as well, in case an author has omitted the presentational attributes.

正如 Andy 在Relative Sizing With EM units中所写,图标现在将随文本自动缩放:0.75emis relative to the font-sizefor the context。例如:

As Andy wrote in Relative Sizing With EM units, the icon will now scale automatically with the text: 0.75em is relative to the font-size for the context. For example:

.small {
  font-size: 0.75em;
}

.small .icon {
  /* Icon height will automatically be
    0.75 * 0.75em */
}

.big {
  font-size: 1.25em;
}

.big .icon {
  /* Icon height will automatically be
    1.25 * 0.75em */
}

此交互式演示仅在Every Layout站点上可用。

This interactive demo is only available on the Every Layout site.

图标和文本之间的间距

Spacing between icon and text

为了确定我们如何管理图标的间距,我们必须权衡效率与灵活性。在设计系统中,有时不灵活可能是一种美德,因为它加强了规律性和一致性。

To establish how we manage the spacing of our icons, we have to weigh efficiency against flexibility. In design systems, sometimes inflexibility can be a virtue, since it enforces regularity and consistency.

在上下文中考虑我们的十字图标,在按钮元素内和文本“关闭”旁边:

Consider our cross icon in context, inside a button element and alongside the text “Close”:

<button>
  <svg class="icon">...</svg> Close
</button>

注意 SVG 和文本节点之间的空格(unicode 点 U+0020,如果你想科学的话)。这会在图标和文本之间添加一个可见空间,我相信您可以想象得到。现在,你无法控制这个空间。即使在源代码中添加相同种类的额外空格也无济于事,因为它会被浏览器折叠成一个空格。但它是一个合适的空格,因为它匹配同一上下文中任何单词之间的空格。同样,我们将图标视为文本。

Note the space (unicode point U+0020, if you want to be scientific) between the SVG and the text node. This adds a visible space between the icon and the text, as I’m sure you can imagine. Now, you don’t have control over this space. Even adding an extra space of the same variety in the source will not help you, since it will be collapsed down to a single space by the browser. But it is a suitable space, because it matches the space between any words in the same context. Again, we are treating the icon like text.

关于在图标中使用简单文本间距的其他一些巧妙的事情:

A couple of other neat things about using simple text spacing with your icons:

  1. 如果图标单独出现,即使它保留在源中,也不会出现空格(使按钮内的间距看起来不均匀)。在这种情况下它也会崩溃。
  2. 您可以使用dir具有 (right-to-left) 值的属性rtl来从左到右视觉交换图标。图标和文本之间仍会出现空格,因为文本方向(包括间距)已反转。
<button dir="rtl">
  <svg class="icon"></svg> Close
</button>

无论图标在左边还是右边,图标和相邻文本之间都会出现一个空格

Whether the icon is on the left or right, a space appears between the icon and the adjacent text

当我们可以使用 HTML 的一个基本特性来重新配置我们的设计时,这很巧妙,而不是必须编写定制样式并将它们附加到任意类。

It’s neat when we can use a fundamental feature of HTML to reconfigure our design, rather than having to write bespoke styles and attach them to arbitrary classes.

如果你确实想要控制空间的长度,你必须接受复杂性的增加和可重用性的降低:如果不为图标设置上下文以首先删除现存空间是不可能的。在下面的代码中,上下文由.with-iconelement 设置,并通过 make it 消除了单词空间inline-flex

If you do want control of the length of the space, you have to accept an increase in complexity, and a diminishing of reusability: It’s not really possible without setting a context for the icon in order to remove the extant space first. In the following code, the context is set by the .with-icon element and the word space eliminated by making it inline-flex.

.icon {
  height: 0.75em;
  height: 1cap;
  width: 0.75em;
  width: 1cap;
}

.with-icon {
  display: inline-flex;
  align-items: baseline;
}

显示inline-flex值的行为正如它的名字所暗示的那样:它创建一个flex上下文,但是创建该上下文的元素本身显示为内联。使用inline-flex消除了单词空间,使我们可以自由地创建纯粹带有边距的空间/间隙。

The inline-flex display value behaves as its name suggests: it creates a flex context, but the element creating that context itself displays as inline. Employing inline-flex eliminates the word space, freeing us to create a space/gap purely with margin.

左:默认出现一个空格。 中心:inline-flex 消除了它。 右:然后你可以添加边距

Left: a space appear by default. Center: inline-flex eliminates it. Right: Then you can add a margin

现在我们可以添加一些保证金。我们如何添加它,使其始终出现在正确的位置,就像空格一样?如果我使用margin-right: 0.5em,它将在图标位于文本之前的左侧位置工作。但是,如果我dir="rtl"在右侧添加边距,则会在错误的一侧产生间隙。

Now we can add some margin. How do we add it in such a way that it always appears in the correct place, like the space did? If I use margin-right: 0.5em, it will work where the icon is on the left, before the text. But if I add dir="rtl" that margin remains on the right, creating a gap on the wrong side.

答案是CSS 逻辑属性。虽然margin-topmargin-rightmargin-bottommargin-left都与物理方向和放置有关,但逻辑属性遵循内容的方向。这取决于流程和书写方向,如Boxes中所述。

The answer is CSS Logical Properties. While margin-top, margin-right, margin-bottom, and margin-left all pertain to physical orientation and placement, logical properties honor instead the direction of the content. This differs depending on the flow and writing direction, as explained in Boxes.

在这种情况下,我会使用margin-inline-endicon 元素。这在文本方向的元素之后应用边距(因此-inline-):

In this case, I would use margin-inline-end with the icon element. This applies margin after the element in the direction of the text (hence -inline-):

.icon {
  height: 0.75em;
  height: 1cap;
  width: 0.75em;
  width: 1cap;
}

.with-icon {
  display: inline-flex;
  align-items: baseline;
}

.with-icon .icon {
  margin-inline-end: var(--space, 0.5em);
}

左侧:从左到右的方向显示图标右侧的结束。 右侧:从右到左的方向显示图标左侧的末端。

Left side: Left to right direction shows end on right side of icon. Right side: Right to left direction shows end on left side of icon.

这种更灵活的间距方法的一个缺点是即使没有提供文本,边距也会保留。不幸的是,虽然您可以使用 定位单独的元素,但您不能定位没有文本节点的:only-child单独元素。所以不可能只用 CSS 去除边距。

One disadvantage with this more flexible approach to spacing is that the margin remains even where text is not supplied. Unfortunately, although you can target lone elements with :only-child, you cannot target lone elements unaccompanied by text nodes. So it is not possible to remove the margin with just CSS.

相反,您可以只删除with-icon该类,因为它只会为手动间距创造条件margin。这样,空间将保留(并按照描述自动折叠)。在即将到来的自定义元素实现中,只有space提供了 prop 才会<icon-l>成为inline-flex元素,并删除单词空格。

Instead, you could just remove the with-icon class, since it only creates the conditions for manual spacing by margin. This way, spaces will remain (and collapse automatically as described). In the custom element implementation to come, only if the space prop is supplied will the <icon-l> be made an inline-flex element, and the word space removed.

用例

Use cases

你以前见过图标,对吧?大多数情况下,您会发现它们作为按钮控件或链接的一部分,用视觉提示补充标签。我们的控件常常使用图标。这对于非常熟悉的图标/符号来说是可以的,比如前面示例中的十字图标,但更深奥的图标可能应该带有文本描述——至少在界面使用的早期阶段。

You’ve seen icons before, right? Most frequently you find them as part of button controls or links, supplementing a label with a visual cue. Too often our controls only use icons. This is okay for highly familiar icons/symbols like the cross icon from previous examples, but more esoteric icons should probably come with a textual description — at least in the early stages of the interface’s usage.

如果没有提供(可见的)文本标签,重要的是至少有某种形式的屏幕阅读器可感知的标签。您可以执行以下操作之一:

Where no (visible) textual label is provided, it’s important there is at least a screen reader perceptible label of some form. You can do one of the following:

  1. 视觉隐藏文本标签(可能在 中提供<span>
  2. 添加一个<title><svg>
  3. aria-label直接向父<button>元素添加一个

组件中,如果将labelprop 添加到<icon-l>,元素本身将被视为图像,role="img"aria-label="[the label value]"应用。屏幕阅读器遇到外部按钮或链接时,会将图标识别为图像或图形,并将aria-label值读出。<icon-l>放置在按钮或链接的位置,图像作用不被宣布。伪图像元素只是用作标签。

In the component, if a label prop is added to <icon-l>, the element itself is treated as an image, with role="img" and aria-label="[the label value]" applied. Encountered by screen reader outside of a button or link, the icon will be identified as an image or graphic, and the aria-label value read out. Where <icon-l> is placed inside a button or link, the image role is not announced. The pseudo-image element is simply purposed as the label.

发电机

The generator

使用此工具生成基本的图标CSS 和 HTML。

Use this tool to generate basic Icon CSS and HTML.

代码生成器工具仅在随附站点中可用。这是基本的解决方案,带有注释。

The code generator tool is only available in the accompanying site. Here is the basic solution, with comments.

HTML

HTML

我们可以使用该<use>元素来嵌入来自远程icons.svg文件的图标。

We can employ the <use> element to embed the icon from a remote icons.svg file.

<span class="with-icon">
  <svg class="icon">
    <use href="/path/to/icons.svg#cross"></use>
  </svg>
  Close
</span>

CSS

CSS

with-icon仅当您希望消除自然单词空间并改为使用时才需要该课程margin

The with-icon class is only necessary if you wish to eliminate the natural word space and employ margin instead.

.icon {
  height: 0.75em;
  /* ↓ Override the em value with `1cap`
  where `cap` is supported */
  height: 1cap;
  width: 0.75em;
  width: 1cap;
}

.with-icon {
  /* ↓ Set the `inline-flex` context,
  which eliminates the word space */
  display: inline-flex;
  align-items: baseline;
}

.with-icon .icon {
  /* ↓ Use the logical margin property
  and a --space variable with a fallback */
  margin-inline-end: var(--space, 0.5em);
}

正如我们的博客文章《无需 JavaScript 的动态 CSS 组件》style中所述,您可以使用属性以声明方式调整元素本身的空间值:

As outlined in our blog post Dynamic CSS Components Without JavaScript, you can adjust the space value declaratively, on the element itself, using the style attribute:

<span class="with-icon">
  <svg class="icon" style="--space: 0.333em">
    <use href="/images/icons/icons.svg#cross"></use>
  </svg>
  Close
</span>

组件

The component

图标的自定义元素实现可供下载

A custom element implementation of the Icon is available for download.

道具接口

Props API

以下道具(属性)将导致Icon组件在更改时重新呈现。它们可以在浏览器开发人员工具中手动更改,或者作为继承的应用程序状态的主题。

The following props (attributes) will cause the Icon component to re-render when altered. They can be altered by hand—in browser developer tools—or as the subjects of inherited application state.

姓名 类型 默认 描述
空间 string null 文本和图标之间的空间。如果为空,则保留自然字间距
标签 string null 在辅助技术中将元素转换为图像并添加值的 aria-label

例子

Examples

带有图标和随附文本的按钮

Button with icon and accompanying text

在以下示例中,<icon-l>为按钮提供了一个图标和随附的文本。假定按钮的可访问名称,这意味着该按钮将在屏幕阅读器软件中宣布为“关闭按钮”(或等效名称)。SVG 被忽略,因为它不提供文本信息。

In the following example, the <icon-l> provides an icon and accompanying text to a button. The assumes the button’s accessible name, meaning the button will be announced as “Close, button” (or equivalent) in screen reader software. The SVG is ignored, since it provides no textual information.

0.5em在这种情况下,已设置明确的空间/边距。

In this case, an explicit space/margin of 0.5em has been set.

<button>
  <icon-l space="0.5em">
    <svg>
      <use href="/images/icons/icons.svg#cross"></use>
    </svg>
    Close
  </icon-l>
</button>

只有一个图标的按钮

Button with just an icon

如果没有提供随附的文本,则按钮有没有可访问名称的危险。通过提供label道具,将<icon-l>其作为带标签的图像传达给屏幕阅读器软件(使用role="img"aria-label="[the prop value]")。这是编写的代码:

Where not accompanying text is provided, the button is in danger of not having an accessible name. By providing a label prop, the <icon-l> is communicated as a labeled image to screen reader software (using role="img" and aria-label="[the prop value]"). This is the authored code:

<button>
  <icon-l label="Close">
    <svg>
      <use href="/path/to/icons.svg#cross"></use>
    </svg>
  </icon-l>
</button>

这是实例化后的代码:

And this is the code after instantiation:

<button>
  <icon-l space="0.5em" label="Close" role="img" aria-label="Close">
    <svg>
      <use href="/path/to/icons.svg#cross"></use>
    </svg>
  </icon-l>
</button>

Original text